This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-3476-scoped-constants in repository https://gitbox.apache.org/repos/asf/struts.git
commit b2a5b0d058eaae229bb950eb79a1d4b7b06569b7 Author: Lukasz Lenart <[email protected]> AuthorDate: Sat Oct 10 17:08:54 2026 +0200 WW-3476 feat(config): store scoped constants on PackageConfig Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../struts2/config/entities/PackageConfig.java | 43 ++++++++++++ .../struts2/config/entities/PackageConfigTest.java | 82 ++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/core/src/main/java/org/apache/struts2/config/entities/PackageConfig.java b/core/src/main/java/org/apache/struts2/config/entities/PackageConfig.java index 391ead116..85f580a82 100644 --- a/core/src/main/java/org/apache/struts2/config/entities/PackageConfig.java +++ b/core/src/main/java/org/apache/struts2/config/entities/PackageConfig.java @@ -59,6 +59,8 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, protected boolean isFinal = false; // a final package is unextendable protected boolean needsRefresh; protected boolean strictMethodInvocation = true; + protected Map<String, String> scopedConstants; + protected Map<String, String> allScopedConstants; protected PackageConfig(String name) { this.name = name; @@ -69,6 +71,8 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, resultTypeConfigs = new LinkedHashMap<>(); globalExceptionMappingConfigs = new ArrayList<>(); parents = new ArrayList<>(); + scopedConstants = new LinkedHashMap<>(); + allScopedConstants = Collections.emptyMap(); } protected PackageConfig(PackageConfig orig) { @@ -90,6 +94,8 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, this.parents = new ArrayList<>(orig.parents); this.location = orig.location; this.strictMethodInvocation = orig.strictMethodInvocation; + this.scopedConstants = new LinkedHashMap<>(orig.scopedConstants); + this.allScopedConstants = orig.allScopedConstants; } public boolean isAbstract() { @@ -189,6 +195,22 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, return retMap; } + /** + * @return scoped constants declared by this package itself, without inherited ones + * @since 7.5.0 + */ + public Map<String, String> getScopedConstants() { + return scopedConstants; + } + + /** + * @return scoped constants in effect for this package: inherited ones, overridden by its own + * @since 7.5.0 + */ + public Map<String, String> getAllScopedConstants() { + return allScopedConstants; + } + /** * returns the List of all the ExceptionMappingConfigs available in the current package. * ExceptionMappingConfigs defined in ancestor packages will be included in this list. @@ -391,6 +413,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, if (!Objects.equals(globalExceptionMappingConfigs, that.globalExceptionMappingConfigs)) return false; if (!Objects.equals(parents, that.parents)) return false; + if (!Objects.equals(scopedConstants, that.scopedConstants)) return false; if (!Objects.equals(defaultInterceptorRef, that.defaultInterceptorRef)) return false; if (!Objects.equals(defaultActionRef, that.defaultActionRef)) @@ -412,6 +435,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, result = 31 * result + (resultTypeConfigs != null ? resultTypeConfigs.hashCode() : 0); result = 31 * result + (globalExceptionMappingConfigs != null ? globalExceptionMappingConfigs.hashCode() : 0); result = 31 * result + (parents != null ? parents.hashCode() : 0); + result = 31 * result + (scopedConstants != null ? scopedConstants.hashCode() : 0); result = 31 * result + (defaultInterceptorRef != null ? defaultInterceptorRef.hashCode() : 0); result = 31 * result + (defaultActionRef != null ? defaultActionRef.hashCode() : 0); result = 31 * result + (defaultResultType != null ? defaultResultType.hashCode() : 0); @@ -605,6 +629,14 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, return this; } + /** + * @since 7.5.0 + */ + public Builder addScopedConstant(String name, String value) { + target.scopedConstants.put(name, value); + return this; + } + public Builder location(Location loc) { target.location = loc; return this; @@ -655,11 +687,22 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>, target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs); target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs); target.parents = Collections.unmodifiableList(target.parents); + target.scopedConstants = Collections.unmodifiableMap(target.scopedConstants); + target.allScopedConstants = mergeScopedConstants(target); PackageConfig result = target; target = new PackageConfig(result); return result; } + private static Map<String, String> mergeScopedConstants(PackageConfig config) { + Map<String, String> merged = new LinkedHashMap<>(); + for (PackageConfig parent : config.parents) { + merged.putAll(parent.getAllScopedConstants()); + } + merged.putAll(config.scopedConstants); + return Collections.unmodifiableMap(merged); + } + @Override public String toString() { return "[BUILDER] " + target.toString(); diff --git a/core/src/test/java/org/apache/struts2/config/entities/PackageConfigTest.java b/core/src/test/java/org/apache/struts2/config/entities/PackageConfigTest.java index b0fd702d7..8619a8b5c 100644 --- a/core/src/test/java/org/apache/struts2/config/entities/PackageConfigTest.java +++ b/core/src/test/java/org/apache/struts2/config/entities/PackageConfigTest.java @@ -20,6 +20,10 @@ package org.apache.struts2.config.entities; import org.apache.struts2.XWorkTestCase; +import java.util.Map; + +import static org.junit.Assert.assertThrows; + public class PackageConfigTest extends XWorkTestCase { public void testFullDefaultInterceptorRef() { @@ -92,4 +96,82 @@ public class PackageConfigTest extends XWorkTestCase { assertFalse(child.isStrictMethodInvocation()); } + public void testScopedConstantsChildOverridesParent() { + PackageConfig parent = new PackageConfig.Builder("parent") + .addScopedConstant("first", "parent-first") + .addScopedConstant("second", "parent-second") + .build(); + + PackageConfig child = new PackageConfig.Builder("child") + .addParent(parent) + .addScopedConstant("second", "child-second") + .build(); + + assertEquals(Map.of("second", "child-second"), child.getScopedConstants()); + assertEquals(Map.of("first", "parent-first", "second", "child-second"), child.getAllScopedConstants()); + } + + public void testScopedConstantsInheritedThroughTwoLevels() { + PackageConfig grandparent = new PackageConfig.Builder("grandparent") + .addScopedConstant("first", "grandparent-first") + .build(); + PackageConfig parent = new PackageConfig.Builder("parent") + .addParent(grandparent) + .build(); + + PackageConfig child = new PackageConfig.Builder("child") + .addParent(parent) + .build(); + + assertEquals(Map.of(), child.getScopedConstants()); + assertEquals(Map.of("first", "grandparent-first"), child.getAllScopedConstants()); + } + + public void testScopedConstantsFirstAddedParentWins() { + PackageConfig first = new PackageConfig.Builder("first") + .addScopedConstant("shared", "from-first") + .build(); + PackageConfig second = new PackageConfig.Builder("second") + .addScopedConstant("shared", "from-second") + .build(); + + PackageConfig child = new PackageConfig.Builder("child") + .addParent(first) + .addParent(second) + .build(); + + assertEquals("from-first", child.getAllScopedConstants().get("shared")); + } + + public void testScopedConstantsSurviveBuilderCopy() { + PackageConfig parent = new PackageConfig.Builder("parent") + .addScopedConstant("first", "parent-first") + .build(); + PackageConfig original = new PackageConfig.Builder("child") + .addParent(parent) + .addScopedConstant("second", "child-second") + .build(); + + PackageConfig copy = new PackageConfig.Builder(original).build(); + + assertEquals(original.getScopedConstants(), copy.getScopedConstants()); + assertEquals(original.getAllScopedConstants(), copy.getAllScopedConstants()); + } + + public void testScopedConstantsAreUnmodifiableAfterBuild() { + PackageConfig config = new PackageConfig.Builder("pkg") + .addScopedConstant("first", "value") + .build(); + + assertThrows(UnsupportedOperationException.class, () -> config.getScopedConstants().put("x", "y")); + assertThrows(UnsupportedOperationException.class, () -> config.getAllScopedConstants().put("x", "y")); + } + + public void testScopedConstantsTakePartInEquality() { + PackageConfig plain = new PackageConfig.Builder("pkg").build(); + PackageConfig scoped = new PackageConfig.Builder("pkg").addScopedConstant("first", "value").build(); + + assertFalse(plain.equals(scoped)); + } + }
