Fix #168 Explicitly document that ELResolver.getType() is expected to return null if the property is resolved and either the property or the resolver is read-only.
diff --git a/api/src/main/java/jakarta/el/ArrayELResolver.java b/api/src/main/java/jakarta/el/ArrayELResolver.java index 3b32377..a2596ff 100644 --- a/api/src/main/java/jakarta/el/ArrayELResolver.java +++ b/api/src/main/java/jakarta/el/ArrayELResolver.java
@@ -73,9 +73,9 @@ * </p> * * <p> - * Assuming the base is an <code>array</code>, this method will always return - * <code>base.getClass().getComponentType()</code>, which is the most general type of component that can be stored at - * any given index in the array. + * Assuming the base is an <code>array</code> and that this resolver was not constructed in read-only mode, this + * method will return <code>base.getClass().getComponentType()</code>, which is the most general type of component + * that can be stored at any given index in the array. * </p> * * @param context The context of this evaluation. @@ -83,7 +83,8 @@ * @param property The index of the element in the array to return the acceptable type for. Will be coerced into an * integer, but otherwise ignored by this resolver. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws PropertyNotFoundException if the given index is out of bounds for this array. * @throws NullPointerException if context is <code>null</code> * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown @@ -102,6 +103,15 @@ if (index < 0 || index >= Array.getLength(base)) { throw new PropertyNotFoundException(); } + + /* + * The resolver may have been created in read-only mode but the + * array and its elements will always be read-write. + */ + if (isReadOnly) { + return null; + } + return base.getClass().getComponentType(); } return null;
diff --git a/api/src/main/java/jakarta/el/BeanELResolver.java b/api/src/main/java/jakarta/el/BeanELResolver.java index 5905007..d69163d 100644 --- a/api/src/main/java/jakarta/el/BeanELResolver.java +++ b/api/src/main/java/jakarta/el/BeanELResolver.java
@@ -253,16 +253,19 @@ * </p> * * <p> - * The provided property will first be coerced to a <code>String</code>. If there is a <code>BeanInfoProperty</code> for - * this property and there were no errors retrieving it, the <code>propertyType</code> of the - * <code>propertyDescriptor</code> is returned. Otherwise, a <code>PropertyNotFoundException</code> is thrown. + * The provided property will first be coerced to a <code>String</code>. If there is a <code>BeanInfoProperty</code> + * for this property, there were no errors retrieving it and neither the property nor the resolver are read-only, + * the <code>propertyType</code> of the <code>propertyDescriptor</code> is returned. If the property is resolved but + * either the property or the resolver is read-only then {@code null} will be returned. Otherwise, a + * <code>PropertyNotFoundException</code> is thrown. * </p> * * @param context The context of this evaluation. * @param base The bean to analyze. * @param property The name of the property to analyze. Will be coerced to a <code>String</code>. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws NullPointerException if context is <code>null</code> * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not * exist or is not readable. @@ -281,6 +284,11 @@ BeanProperty beanProperty = getBeanProperty(context, base, property); context.setPropertyResolved(true); + + if (isReadOnly || beanProperty.isReadOnly()) { + return null; + } + return beanProperty.getPropertyType(); }
diff --git a/api/src/main/java/jakarta/el/BeanNameELResolver.java b/api/src/main/java/jakarta/el/BeanNameELResolver.java index 31cc068..8b80761 100644 --- a/api/src/main/java/jakarta/el/BeanNameELResolver.java +++ b/api/src/main/java/jakarta/el/BeanNameELResolver.java
@@ -138,8 +138,9 @@ * @param context The context of this evaluation. * @param base <code>null</code> * @param property The name of the bean. - * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the type of the bean with the given name. Otherwise, undefined. + * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code> and + * the associated BeanNameResolver is not read-only then the type of the bean with the given name. If the given + * bean name was resolved but the associated BeanNameResolver is read-only then {@code null}. Otherwise, undefined. * @throws NullPointerException if context is <code>null</code>. * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown * exception must be included as the cause property of this exception, if available. @@ -153,6 +154,14 @@ if (base == null && property instanceof String) { if (beanNameResolver.isNameResolved((String) property)) { context.setPropertyResolved(true); + + /* + * No resolver level isReadOnly property for this resolver + */ + if (beanNameResolver.isReadOnly((String) property)) { + return null; + } + return beanNameResolver.getBean((String) property).getClass(); } }
diff --git a/api/src/main/java/jakarta/el/CompositeELResolver.java b/api/src/main/java/jakarta/el/CompositeELResolver.java index a805e7e..f87d378 100644 --- a/api/src/main/java/jakarta/el/CompositeELResolver.java +++ b/api/src/main/java/jakarta/el/CompositeELResolver.java
@@ -245,7 +245,8 @@ * variable. * @param property The property or variable to return the acceptable type for. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws NullPointerException if context is <code>null</code> * @throws PropertyNotFoundException if the given (base, property) pair is handled by this <code>ELResolver</code> but * the specified variable or property does not exist or is not readable.
diff --git a/api/src/main/java/jakarta/el/ELResolver.java b/api/src/main/java/jakarta/el/ELResolver.java index 8fa83f0..e951039 100644 --- a/api/src/main/java/jakarta/el/ELResolver.java +++ b/api/src/main/java/jakarta/el/ELResolver.java
@@ -181,12 +181,17 @@ * superclass of the type of the actual element that is currently in the specified array element. * </p> * + * <p> + * If the resolver or the property is read-only, this method must return {@code null}. + * </p> + * * @param context The context of this evaluation. * @param base The base object whose property value is to be analyzed, or <code>null</code> to analyze a top-level * variable. * @param property The property or variable to return the acceptable type for. - * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws PropertyNotFoundException if the given (base, property) pair is handled by this <code>ELResolver</code> but * the specified variable or property does not exist or is not readable. * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
diff --git a/api/src/main/java/jakarta/el/ListELResolver.java b/api/src/main/java/jakarta/el/ListELResolver.java index 8920c3d..ce3edea 100644 --- a/api/src/main/java/jakarta/el/ListELResolver.java +++ b/api/src/main/java/jakarta/el/ListELResolver.java
@@ -78,8 +78,9 @@ * </p> * * <p> - * Assuming the base is a <code>List</code>, this method will always return <code>Object.class</code>. This is because - * <code>List</code>s accept any object as an element. + * Assuming the base is a <code>List</code>, this method will return <code>Object.class</code> unless the resolver + * is constructed in read-only mode in which case {@code null} will be returned. This is because <code>List</code>s + * accept any object as an element. * </p> * * @param context The context of this evaluation. @@ -87,7 +88,8 @@ * @param property The index of the element in the list to return the acceptable type for. Will be coerced into an * integer, but otherwise ignored by this resolver. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws PropertyNotFoundException if the given index is out of bounds for this list. * @throws NullPointerException if context is <code>null</code> * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown @@ -107,6 +109,14 @@ throw new PropertyNotFoundException(); } + /* + * Not perfect as a custom list implementation may be read-only but + * consistent with isReadOnly(). + */ + if (list.getClass() == theUnmodifiableListClass || isReadOnly) { + return null; + } + return Object.class; }
diff --git a/api/src/main/java/jakarta/el/MapELResolver.java b/api/src/main/java/jakarta/el/MapELResolver.java index cacd175..870f827 100644 --- a/api/src/main/java/jakarta/el/MapELResolver.java +++ b/api/src/main/java/jakarta/el/MapELResolver.java
@@ -82,7 +82,8 @@ * </p> * * <p> - * Assuming the base is a <code>Map</code>, this method will always return <code>Object.class</code>. This is because + * Assuming the base is a <code>Map</code>, this method will always return <code>Object.class</code> unless the + * resolver is constructed in read-only mode in which case {@code null} will be returned. This is because * <code>Map</code>s accept any object as the value for a given key. * </p> * @@ -90,7 +91,8 @@ * @param base The map to analyze. Only bases of type <code>Map</code> are handled by this resolver. * @param property The key to return the acceptable type for. Ignored by this resolver. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the most general acceptable type; otherwise undefined. + * the most general acceptable type which must be {@code null} if the either the property or the resolver is + * read-only; otherwise undefined * @throws NullPointerException if context is <code>null</code> * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown * exception must be included as the cause property of this exception, if available. @@ -103,6 +105,12 @@ if (base != null && base instanceof Map) { context.setPropertyResolved(true); + + Map<?, ?> map = (Map<?, ?>) base; + if (isReadOnly || map.getClass() == theUnmodifiableMapClass) { + return null; + } + return Object.class; }
diff --git a/api/src/main/java/jakarta/el/ResourceBundleELResolver.java b/api/src/main/java/jakarta/el/ResourceBundleELResolver.java index e8fb0be..5e066dd 100644 --- a/api/src/main/java/jakarta/el/ResourceBundleELResolver.java +++ b/api/src/main/java/jakarta/el/ResourceBundleELResolver.java
@@ -117,6 +117,10 @@ if (base instanceof ResourceBundle) { context.setPropertyResolved(true); + /* + * ResourceBundles are always read-only so fall-through to return + * null + */ } return null;
diff --git a/api/src/main/java/jakarta/el/StaticFieldELResolver.java b/api/src/main/java/jakarta/el/StaticFieldELResolver.java index c498a50..e64f40b 100644 --- a/api/src/main/java/jakarta/el/StaticFieldELResolver.java +++ b/api/src/main/java/jakarta/el/StaticFieldELResolver.java
@@ -188,14 +188,11 @@ * resolver, before returning. If this property is not <code>true</code> after this method is called, the caller can * safely assume no value has been set. * - * <p> - * If the property string is a public static field of class specified in ELClass, return the type of the static field. - * * @param context The context of this evaluation. * @param base An <code>ELClass</code>. * @param property The name of the field. * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then - * the type of the type of the field. + * <code>null</code>; otherwise undefined. * @throws NullPointerException if context is <code>null</code>. * @throws PropertyNotFoundException if field is not a public static filed of the class, or if the field is * inaccessible. @@ -211,15 +208,16 @@ String fieldName = (String) property; try { context.setPropertyResolved(true); - Field field = klass.getField(fieldName); - - int mod = field.getModifiers(); - if (isPublic(mod) && isStatic(mod)) { - return field.getType(); - } + + klass.getField(fieldName); + + /* + * This resolver is always read-only so fall-through to return + * null. + */ } catch (NoSuchFieldException ex) { + throw new PropertyNotFoundException(getExceptionMessageString(context, "staticFieldReadError", new Object[] { klass.getName(), fieldName })); } - throw new PropertyNotFoundException(getExceptionMessageString(context, "staticFieldReadError", new Object[] { klass.getName(), fieldName })); } return null;