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

rmaucher pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 873da9c55045cb96aa2d14a92e58c56667b55352
Author: opencode <[email protected]>
AuthorDate: Sun Oct 18 14:55:25 2026 +0200

    Resolve imported identifiers in AstIdentifier.getType()
    
    AstIdentifier evaluated an identifier that no resolver handled by
    falling back to the ImportHandler: a name matching an imported class
    yielded an ELClass and a name matching a statically imported field
    yielded the field value. getType() had no equivalent fallback, so for
    these expressions getValue() succeeded while getType() reported
    PropertyNotFoundException. Since ValueExpression.getType() is the API
    used to determine the result type before evaluating, the gap made
    import-resolved identifiers unusable for any client that inspects the
    type first.
    
    Mirror the fallback in getType(). A class import reports ELClass rather
    than Class, since that is the type of the value getValue() returns and
    ELClass is not itself a Class, which keeps the reported type assignable
    from the value. A static field import reports the declared type of the
    field, matching how a bean property of the same type is reported. The
    check stays behind the resolver so a resolver that handles the name
    still takes precedence, as it does when getting the value, and a name
    that resolves nothing continues to report PropertyNotFoundException.
    
    Adds tests for both import kinds, for the resolver precedence and for
    the unchanged error for unresolvable names. The import tests fail
    against the previous implementation.
---
 java/org/apache/el/parser/AstIdentifier.java     | 23 +++++-
 test/org/apache/el/parser/TestAstIdentifier.java | 95 ++++++++++++++++++++++++
 2 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/el/parser/AstIdentifier.java 
b/java/org/apache/el/parser/AstIdentifier.java
index a02b10d105..3de3a3560a 100644
--- a/java/org/apache/el/parser/AstIdentifier.java
+++ b/java/org/apache/el/parser/AstIdentifier.java
@@ -50,10 +50,27 @@ public final class AstIdentifier extends SimpleNode {
         }
         ctx.setPropertyResolved(false);
         Class<?> result = ctx.getELResolver().getType(ctx, null, this.image);
-        if (!ctx.isPropertyResolved()) {
-            throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
+        if (ctx.isPropertyResolved()) {
+            return result;
         }
-        return result;
+
+        // Import
+        Object imported = ctx.getImportHandler().resolveClass(this.image);
+        if (imported != null) {
+            // getValue() returns an ELClass for an imported class
+            return ELClass.class;
+        }
+        imported = ctx.getImportHandler().resolveStatic(this.image);
+        if (imported != null) {
+            // getValue() returns the value of a statically imported field
+            try {
+                return ((Class<?>) imported).getField(this.image).getType();
+            } catch (IllegalArgumentException | NoSuchFieldException | 
SecurityException e) {
+                throw new ELException(e);
+            }
+        }
+
+        throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
     }
 
 
diff --git a/test/org/apache/el/parser/TestAstIdentifier.java 
b/test/org/apache/el/parser/TestAstIdentifier.java
index b9dfec5df5..fb4e4487a6 100644
--- a/test/org/apache/el/parser/TestAstIdentifier.java
+++ b/test/org/apache/el/parser/TestAstIdentifier.java
@@ -16,10 +16,13 @@
  */
 package org.apache.el.parser;
 
+import jakarta.el.ELClass;
 import jakarta.el.ELContext;
 import jakarta.el.ELException;
 import jakarta.el.ELProcessor;
+import jakarta.el.ELResolver;
 import jakarta.el.ExpressionFactory;
+import jakarta.el.PropertyNotFoundException;
 import jakarta.el.ValueExpression;
 
 import org.junit.Assert;
@@ -52,6 +55,98 @@ public class TestAstIdentifier {
     }
 
 
+    @Test
+    public void testImport03() {
+        // getType() must resolve an imported class, just as getValue() does
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${Integer}", Object.class);
+        Assert.assertNotNull(ve.getValue(context));
+        Assert.assertEquals(ELClass.class, ve.getType(context));
+    }
+
+
+    @Test
+    public void testImport04() {
+        // getType() must resolve a statically imported field, just as
+        // getValue() does. The declared type of the field is reported, in the
+        // same way as for a bean property.
+        ELProcessor processor = new ELProcessor();
+        
processor.getELManager().getELContext().getImportHandler().importStatic(
+                "java.lang.Integer.MAX_VALUE");
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${MAX_VALUE}", Object.class);
+        Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE), 
ve.getValue(context));
+        Assert.assertEquals(Integer.TYPE, ve.getType(context));
+    }
+
+
+    @Test
+    public void testImport05() {
+        // The resolver chain must take precedence over the import fallback in
+        // getType(), just as it does in getValue(). "Integer" is resolvable
+        // both ways: by the custom resolver and via the default class import.
+        ELProcessor processor = new ELProcessor();
+        processor.getELManager().addELResolver(new ELResolver() {
+            @Override
+            public Object getValue(ELContext context, Object base, Object 
property) {
+                if (base == null && "Integer".equals(property)) {
+                    context.setPropertyResolved(null, property);
+                    return "resolved";
+                }
+                return null;
+            }
+
+            @Override
+            public Class<?> getType(ELContext context, Object base, Object 
property) {
+                if (base == null && "Integer".equals(property)) {
+                    context.setPropertyResolved(null, property);
+                    return String.class;
+                }
+                return null;
+            }
+
+            @Override
+            public void setValue(ELContext context, Object base, Object 
property, Object value) {
+            }
+
+            @Override
+            public boolean isReadOnly(ELContext context, Object base, Object 
property) {
+                return false;
+            }
+
+            @Override
+            public Class<?> getCommonPropertyType(ELContext context, Object 
base) {
+                return null;
+            }
+        });
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${Integer}", Object.class);
+        Assert.assertEquals("resolved", ve.getValue(context));
+        Assert.assertEquals(String.class, ve.getType(context));
+    }
+
+
+    @Test
+    public void testUnresolvedGetType() {
+        // A name that getValue() cannot resolve must continue to report a
+        // PropertyNotFoundException from getType()
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${unresolvedName}", Object.class);
+        try {
+            ve.getType(context);
+            Assert.fail("A PropertyNotFoundException was expected");
+        } catch (PropertyNotFoundException e) {
+            // Expected
+        }
+    }
+
+
     @Test
     public void testIdentifierStart() {
         /*


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to