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

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/main by this push:
     new 8c66d8d64 WW-5729 fix(jasperreports): override HashMap.get/containsKey 
in ValueStackShadowMap (#1920)
8c66d8d64 is described below

commit 8c66d8d647ffb4788e5b8a3a17a8c0ab26de8eff
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Sep 12 11:57:33 2026 +0200

    WW-5729 fix(jasperreports): override HashMap.get/containsKey in 
ValueStackShadowMap (#1920)
    
    * WW-5729 fix(jasperreports): override HashMap.get/containsKey in 
ValueStackShadowMap
    
    The two methods took a String parameter, so they overloaded rather than
    overrode HashMap.get(Object) and containsKey(Object). JasperReports holds
    the parameter map as java.util.Map and calls the Object variants, which
    meant the value-stack fallback the class exists for never ran when a
    report was filled: only the entries the result put explicitly reached
    JasperReports, and a declared parameter exposed as an action property
    rendered as null.
    
    The Object overrides were the original code; 2908d746c (2019, "Minor
    code optimization") narrowed them to String when it added the generics,
    and the regression first shipped in 6.0.0. The jasperreports7 copy
    already declares both as @Override with an Object parameter.
    
    Both copies now let an explicit map entry win even when its value is
    null, so containsKey and get agree and JasperReports skips the default
    value expression consistently. The String overloads stay in the 6.x
    plugin as deprecated delegates for binary compatibility.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    * WW-5729 test(jasperreports): cover ValueStackShadowMap directly
    
    The deprecated String delegates are reachable only by callers holding a
    ValueStackShadowMap reference, so the result-level tests never hit them.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../views/jasperreports/ValueStackShadowMap.java   | 33 +++++++---
 .../jasperreports/JasperReportsResultTest.java     | 33 ++++++++++
 .../jasperreports/ValueStackShadowMapTest.java     | 71 ++++++++++++++++++++++
 .../views/jasperreports7/ValueStackShadowMap.java  | 11 ++--
 .../jasperreports7/JasperReport7ResultTest.java    | 23 +++++++
 5 files changed, 155 insertions(+), 16 deletions(-)

diff --git 
a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
 
b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
index 7e78edf75..babc7ddaf 100644
--- 
a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
+++ 
b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
@@ -53,10 +53,11 @@ public class ValueStackShadowMap extends HashMap<String, 
Object> {
      * @return <tt>true</tt>, if contains key, <tt>false</tt> otherwise.
      * @see java.util.HashMap#containsKey
      */
-    public boolean containsKey(String key) {
+    @Override
+    public boolean containsKey(Object key) {
         boolean hasKey = super.containsKey(key);
 
-        if (!hasKey && valueStack.findValue(key) != null) {
+        if (!hasKey && key != null && valueStack.findValue(key.toString()) != 
null) {
             hasKey = true;
         }
 
@@ -67,16 +68,30 @@ public class ValueStackShadowMap extends HashMap<String, 
Object> {
      * Implementation of get(), overriding HashMap implementation.
      *
      * @param key - The key to get in HashMap and if not found there from the 
valueStack.
-     * @return value - The object from HashMap or if null, from the valueStack.
+     * @return value - The object from HashMap or, if the key is absent, from 
the valueStack.
      * @see java.util.HashMap#get
      */
-    public Object get(String key) {
-        Object value = super.get(key);
-
-        if ((value == null)) {
-            value = valueStack.findValue((String) key);
+    @Override
+    public Object get(Object key) {
+        if (key == null || super.containsKey(key)) {
+            return super.get(key);
         }
+        return valueStack.findValue(key.toString());
+    }
+
+    /**
+     * @deprecated since 7.4.0, use {@link #containsKey(Object)}
+     */
+    @Deprecated(since = "7.4.0", forRemoval = true)
+    public boolean containsKey(String key) {
+        return containsKey((Object) key);
+    }
 
-        return value;
+    /**
+     * @deprecated since 7.4.0, use {@link #get(Object)}
+     */
+    @Deprecated(since = "7.4.0", forRemoval = true)
+    public Object get(String key) {
+        return get((Object) key);
     }
 }
diff --git 
a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
 
b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
index beb563c12..82eb41d1f 100644
--- 
a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
+++ 
b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
@@ -171,6 +171,39 @@ public class JasperReportsResultTest extends 
StrutsTestCase {
         assertTrue(response.getContentAsString().contains("Qux Report"));
     }
 
+    public void testDeclaredParameterResolvedFromValueStack() throws Exception 
{
+        result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+        stack.push(new Object() {
+            public String getTitle() {
+                return "Shadow";
+            }
+        });
+
+        result.execute(this.invocation);
+
+        assertTrue(response.getContentAsString().contains("Shadow Report"));
+    }
+
+    public void testExplicitNullParameterIsNotShadowedByValueStack() throws 
Exception {
+        result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+        stack.push(new Object() {
+            public String getTitle() {
+                return "Shadow";
+            }
+
+            public Map<String, Object> getReportParameters() {
+                Map<String, Object> params = new HashMap<>();
+                params.put("title", null);
+                return params;
+            }
+        });
+        result.setReportParameters("reportParameters");
+
+        result.execute(this.invocation);
+
+        assertTrue(response.getContentAsString().contains("null Report"));
+    }
+
     public void testFillFromReportParametersWithoutDataSourceOrConnection() 
throws Exception {
         stack.push(new Object() {
             public Map<String, Object> getReportParameters() {
diff --git 
a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java
 
b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java
new file mode 100644
index 000000000..b6c905ec7
--- /dev/null
+++ 
b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.views.jasperreports;
+
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.junit.StrutsTestCase;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Map;
+
+public class ValueStackShadowMapTest extends StrutsTestCase {
+
+    private ValueStackShadowMap map;
+
+    public void testFallsBackToValueStackThroughMapInterface() {
+        Map<String, Object> asMap = map;
+
+        assertTrue(asMap.containsKey("title"));
+        assertEquals("Shadow", asMap.get("title"));
+        assertFalse(asMap.containsKey("missing"));
+        assertNull(asMap.get("missing"));
+    }
+
+    public void testExplicitEntryWinsOverValueStack() {
+        map.put("title", "Explicit");
+        map.put("other", null);
+
+        assertEquals("Explicit", map.get((Object) "title"));
+        assertTrue(map.containsKey((Object) "other"));
+        assertNull(map.get((Object) "other"));
+    }
+
+    public void testNullKeyDoesNotReachValueStack() {
+        assertFalse(map.containsKey((Object) null));
+        assertNull(map.get((Object) null));
+    }
+
+    @SuppressWarnings("removal")
+    public void testStringOverloadsDelegateToOverrides() {
+        assertTrue(map.containsKey("title"));
+        assertEquals("Shadow", map.get("title"));
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ValueStack stack = ActionContext.getContext().getValueStack();
+        stack.push(new Object() {
+            public String getTitle() {
+                return "Shadow";
+            }
+        });
+        map = new ValueStackShadowMap(stack);
+    }
+}
diff --git 
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
 
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
index 12aa3c871..1022a6407 100644
--- 
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
+++ 
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
@@ -66,17 +66,14 @@ public class ValueStackShadowMap extends HashMap<String, 
Object> {
      * Implementation of get(), overriding HashMap implementation.
      *
      * @param key - The key to get in HashMap and if not found there from the 
valueStack.
-     * @return value - The object from HashMap or if null, from the valueStack.
+     * @return value - The object from HashMap or, if the key is absent, from 
the valueStack.
      * @see java.util.HashMap#get
      */
     @Override
     public Object get(Object key) {
-        Object value = super.get(key);
-
-        if (key != null && value == null) {
-            value = valueStack.findValue(key.toString());
+        if (key == null || super.containsKey(key)) {
+            return super.get(key);
         }
-
-        return value;
+        return valueStack.findValue(key.toString());
     }
 }
diff --git 
a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
 
b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
index d1f82d5dc..c334dd6e8 100644
--- 
a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
+++ 
b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
@@ -197,6 +197,29 @@ public class JasperReport7ResultTest extends 
StrutsTestCase {
         assertThat(response.getContentAsString()).contains("Baz Report");
     }
 
+    public void testExplicitNullParameterIsNotShadowedByValueStack() throws 
Exception {
+        // given
+        result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+        stack.push(new Object() {
+            public String getTitle() {
+                return "Shadow";
+            }
+
+            public Map<String, Object> getReportParameters() {
+                Map<String, Object> params = new HashMap<>();
+                params.put("title", null);
+                return params;
+            }
+        });
+        result.setReportParameters("reportParameters");
+
+        // when
+        result.execute(this.invocation);
+
+        // then
+        assertThat(response.getContentAsString()).contains("null Report");
+    }
+
     public void testFillFromReportParametersWithoutDataSourceOrConnection() 
throws Exception {
         // given
         stack.push(new Object() {

Reply via email to