You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2026/06/03 17:05:15 UTC

(myfaces) branch 4.1.x updated (b4a4050f6 -> 46ec9b484)

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

tandraschko pushed a change to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git


    from b4a4050f6 [perf] optimize UIData/UIRepeat state saving
     new b1f25c3a4 remove unncessary check
     new 633ca827c [perf] cache event listeners list, optimize popComponentFromEL
     new 5952afa90 [perf] avoid new String creation
     new f486f75c4 [perf] reuse variables
     new 46ec9b484 fixed build

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/jakarta/faces/component/UIComponent.java  | 136 ++++++++--------
 .../jakarta/faces/component/UIComponentBase.java   |  51 +++---
 .../faces/component/_ComponentAttributesMap.java   |  26 ++--
 .../jakarta/faces/component/_DeltaStateHelper.java |  16 +-
 .../component/behavior/_DeltaStateHelper.java      |   7 +-
 .../renderkit/html/HtmlResponseWriterImpl.java     | 171 ++++++++++-----------
 .../renderkit/html/HtmlResponseWriterImplTest.java |  87 +++++++++++
 7 files changed, 294 insertions(+), 200 deletions(-)


(myfaces) 01/05: remove unncessary check

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git

commit b1f25c3a49f2e69c79479bdfd1fb6b0fc103ed13
Author: tandraschko <ta...@apache.org>
AuthorDate: Wed Jun 3 18:25:35 2026 +0200

    remove unncessary check
    
    (cherry picked from commit 29c8e357119d13f90b1993773fddbb39fef76574)
---
 .../main/java/jakarta/faces/component/UIComponentBase.java    | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/api/src/main/java/jakarta/faces/component/UIComponentBase.java b/api/src/main/java/jakarta/faces/component/UIComponentBase.java
index bef6f8360..9f5bb1eb4 100755
--- a/api/src/main/java/jakarta/faces/component/UIComponentBase.java
+++ b/api/src/main/java/jakarta/faces/component/UIComponentBase.java
@@ -579,13 +579,9 @@ public void encodeChildren(FacesContext context) throws IOException
     {
         Assert.notNull(context, "context");
 
-        boolean isCachedFacesContext = isCachedFacesContext();
         try
         {
-            if (!isCachedFacesContext)
-            {
-                setCachedFacesContext(context);
-            }
+            setCachedFacesContext(context);
             if (isRendered())
             {
                 // If our rendered property is true, render the child UIComponents of this UIComponent.
@@ -616,10 +612,7 @@ public void encodeChildren(FacesContext context) throws IOException
         }
         finally
         {
-            if (!isCachedFacesContext)
-            {
-                setCachedFacesContext(null);
-            }
+            setCachedFacesContext(null);
         }
     }
 


(myfaces) 02/05: [perf] cache event listeners list, optimize popComponentFromEL

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git

commit 633ca827cef53c842346ef6e6d7996688238b145
Author: tandraschko <ta...@apache.org>
AuthorDate: Wed Jun 3 18:45:07 2026 +0200

    [perf] cache event listeners list, optimize popComponentFromEL
---
 .../java/jakarta/faces/component/UIComponent.java  | 136 ++++++++++++---------
 .../jakarta/faces/component/UIComponentBase.java   |  40 +++---
 2 files changed, 99 insertions(+), 77 deletions(-)

diff --git a/api/src/main/java/jakarta/faces/component/UIComponent.java b/api/src/main/java/jakarta/faces/component/UIComponent.java
index 9dd7e7732..1e926acf5 100755
--- a/api/src/main/java/jakarta/faces/component/UIComponent.java
+++ b/api/src/main/java/jakarta/faces/component/UIComponent.java
@@ -119,7 +119,13 @@ public abstract class UIComponent
 
     private static final String _CURRENT_COMPOSITE_COMPONENT_KEY = "compositeComponent:" + UIComponent.class.getName();
 
-    Map<Class<? extends SystemEvent>, List<SystemEventListener>> _systemEventListenerClassMap;
+    Map<Class<? extends SystemEvent>, List<SystemEventListener>> _systemEventListeners;
+
+    // Cached unmodifiable views of _systemEventListeners entries.
+    // Created lazily; the whole cache is cleared whenever a backing list reference is
+    // replaced (full restore or non-delta entry in delta restore) — see UIComponentBase.
+    // Package-private so UIComponentBase can reset it after those restore operations.
+    transient Map<Class<? extends SystemEvent>, List<SystemEventListener>> _unmodifiableSystemEventListeners;
 
     /**
      * Used to cache the map created using getResourceBundleMap() method, since this method could be called several
@@ -139,12 +145,6 @@ public abstract class UIComponent
      */
     private boolean _initialStateMarked = false;
 
-    /**
-     * @deprecated
-     */
-    @Deprecated
-    protected Map<String, ValueExpression> bindings;
-
     public UIComponent()
     {
     }
@@ -341,7 +341,7 @@ public void setValueExpression(String name, ValueExpression expression)
 
         if (expression == null)
         {
-            getStateHelper().remove(PropertyKeys.bindings, name);
+            getStateHelper().remove(PropertyKeys.valueExpressions, name);
         }
         else
         {
@@ -359,7 +359,7 @@ public void setValueExpression(String name, ValueExpression expression)
                 }
             }
 
-            getStateHelper().put(PropertyKeys.bindings, name, expression);
+            getStateHelper().put(PropertyKeys.valueExpressions, name, expression);
         }
     }
 
@@ -448,25 +448,26 @@ public static UIComponent getCurrentCompositeComponent(FacesContext context)
     @Override
     public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass)
     {
-        List<SystemEventListener> listeners;
-        if (_systemEventListenerClassMap == null)
+        if (_systemEventListeners == null)
         {
-            listeners = Collections.emptyList();
+            return Collections.emptyList();
         }
-        else
+        List<SystemEventListener> listeners = _systemEventListeners.get(eventClass);
+        if (listeners == null)
         {
-            listeners = _systemEventListenerClassMap.get(eventClass);
-            if (listeners == null)
-            {
-                listeners = Collections.emptyList();
-            }
-            else
-            {
-                listeners = Collections.unmodifiableList(listeners);
-            }
+            return Collections.emptyList();
         }
-
-        return listeners;
+        if (_unmodifiableSystemEventListeners == null)
+        {
+            _unmodifiableSystemEventListeners = new HashMap<>(4, 1f);
+        }
+        List<SystemEventListener> unmodifiableList = _unmodifiableSystemEventListeners.get(eventClass);
+        if (unmodifiableList == null)
+        {
+            unmodifiableList = Collections.unmodifiableList(listeners);
+            _unmodifiableSystemEventListeners.put(eventClass, unmodifiableList);
+        }
+        return unmodifiableList;
     }
 
     /**
@@ -617,10 +618,11 @@ public ValueExpression getValueExpression(String name)
     {
         Assert.notNull(name, "name");
 
-        Map<String, Object> bindings = (Map<String, Object>) getStateHelper().get(PropertyKeys.bindings);
-        if (bindings != null)
+        Map<String, Object> valueExpressions = (Map<String, Object>)
+                getStateHelper().get(PropertyKeys.valueExpressions);
+        if (valueExpressions != null)
         {
-            return (ValueExpression) bindings.get(name);
+            return (ValueExpression) valueExpressions.get(name);
         }
 
         return null;
@@ -636,6 +638,22 @@ public ValueExpression getValueExpression(String name)
 
     public abstract UIComponent getFacet(String name);
 
+    /**
+     * <p>
+     * Convenience method to return the named facet using an enum identifier, if it exists, or <code>null</code>
+     * otherwise. This method delegates to {@link #getFacet(String)} using the result of calling
+     * <code>identifier.toString()</code>. If the requested facet does not exist, the facets Map must not be created.
+     * </p>
+     *
+     * @param identifier Enum identifier of the desired facet
+     * @return the component, or <code>null</code>.
+     * @since 5.0
+     */
+    public UIComponent getFacet(Enum<?> identifier)
+    {
+        return getFacet(identifier.toString());
+    }
+
     public abstract Iterator<UIComponent> getFacetsAndChildren();
 
     public abstract void broadcast(FacesEvent event) throws AbortProcessingException;
@@ -701,7 +719,7 @@ public void encodeAll(FacesContext context) throws IOException
 
     protected abstract void addFacesListener(FacesListener listener);
 
-    protected abstract FacesListener[] getFacesListeners(Class clazz);
+    protected abstract FacesListener[] getFacesListeners(Class<? extends FacesListener> clazz);
 
     protected abstract void removeFacesListener(FacesListener listener);
 
@@ -756,8 +774,8 @@ public void processEvent(ComponentSystemEvent event) throws AbortProcessingExcep
                         // Check if the listener points again to the component, to
                         // avoid StackoverflowException
                         boolean shouldProcessEvent = true;
-                        if (listener instanceof _EventListenerWrapper && 
-                            ((_EventListenerWrapper) listener).getListenerCapability() ==
+                        if (listener instanceof _EventListenerWrapper wrapper && 
+                            wrapper.getListenerCapability() ==
                                 _EventListenerWrapper.LISTENER_TYPE_COMPONENT)
                         {
                             shouldProcessEvent = false;
@@ -790,16 +808,16 @@ public void subscribeToEvent(Class<? extends SystemEvent> eventClass,
         SystemEventListener listener = new _EventListenerWrapper(this, componentListener);
 
         // Make sure the map exists
-        if (_systemEventListenerClassMap == null)
+        if (_systemEventListeners == null)
         {
-            _systemEventListenerClassMap = new HashMap<>(4, 1f);
+            _systemEventListeners = new HashMap<>(4, 1f);
         }
 
-        List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
+        List<SystemEventListener> listeners = _systemEventListeners.get(eventClass);
         if (listeners == null)
         {
             listeners = new _DeltaList<>(3);
-            _systemEventListenerClassMap.put(eventClass, listeners);
+            _systemEventListeners.put(eventClass, listeners);
             listeners.add(listener);
         }
         else
@@ -829,9 +847,9 @@ public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass,
         Assert.notNull(eventClass, "eventClass");
         Assert.notNull(componentListener, "componentListener");
 
-        if (_systemEventListenerClassMap != null)
+        if (_systemEventListeners != null)
         {
-            List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
+            List<SystemEventListener> listeners = _systemEventListeners.get(eventClass);
             if (listeners != null && !listeners.isEmpty())
             {
                 for (Iterator<SystemEventListener> it = listeners.iterator(); it.hasNext(); )
@@ -907,11 +925,11 @@ public boolean visitTree(VisitContext context, VisitCallback callback)
                     int childCount = getChildCount();
                     if (childCount > 0)
                     {
-                    List<UIComponent> children = getChildren();
-                    for (int i = 0; i < childCount; i++)
-                    {
-                        UIComponent child = children.get(i);
-                        if (child.visitTree(context, callback))
+                        List<UIComponent> children = getChildren();
+                        for (int i = 0; i < childCount; i++)
+                        {
+                            UIComponent child = children.get(i);
+                            if (child.visitTree(context, callback))
                             {
                                 return true;
                             }
@@ -929,7 +947,7 @@ public boolean visitTree(VisitContext context, VisitCallback callback)
 
     protected abstract FacesContext getFacesContext();
 
-    protected abstract Renderer getRenderer(FacesContext context);
+    protected abstract <T extends UIComponent> Renderer<T> getRenderer(FacesContext context);
 
     /**
      * Note that id, clientId properties
@@ -956,7 +974,7 @@ enum PropertyKeys
         rendered,
         rendererType,
         attributesMap,
-        bindings,
+        valueExpressions,
         facesListeners,
         passThroughAttributesMap
     }
@@ -1028,29 +1046,27 @@ public void popComponentFromEL(FacesContext context)
         UIComponent oldCurrent = null;
         if (componentStack != null && !componentStack.isEmpty())
         {
-            int size = componentStack.size();
-            int componentIndex;
-            // Fast path: in the normal (non-error) case this component is always at the top
-            // of the stack, so check the last slot with an O(1) identity test before falling
-            // back to the O(n) lastIndexOf scan.
-            if (componentStack.get(size - 1) == this)
+            int lastIndex = componentStack.size() - 1;
+            if (componentStack.get(lastIndex) == this)
             {
-                componentIndex = size - 1;
+                // Fast path: balanced push/pop — this component is the top of the stack.
+                oldCurrent = componentStack.remove(lastIndex);
             }
             else
             {
-                componentIndex = componentStack.lastIndexOf(this);
-            }
-            if (componentIndex >= 0)
-            {
-                for (int i = componentStack.size()-1; i >= componentIndex ; i--)
+                // Slow path: unbalanced pop (e.g. an intervening error skipped some pops).
+                int componentIndex = componentStack.lastIndexOf(this);
+                if (componentIndex >= 0)
                 {
-                    oldCurrent = componentStack.remove(componentStack.size()-1);
+                    for (int i = lastIndex; i >= componentIndex; i--)
+                    {
+                        oldCurrent = componentStack.remove(componentStack.size() - 1);
+                    }
+                }
+                else
+                {
+                    return;
                 }
-            }
-            else
-            {
-                return;
             }
         }
 
diff --git a/api/src/main/java/jakarta/faces/component/UIComponentBase.java b/api/src/main/java/jakarta/faces/component/UIComponentBase.java
index 9f5bb1eb4..bfb1d0e41 100755
--- a/api/src/main/java/jakarta/faces/component/UIComponentBase.java
+++ b/api/src/main/java/jakarta/faces/component/UIComponentBase.java
@@ -428,10 +428,10 @@ public void clearInitialState()
                 ((PartialStateHolder) entry.getValue()).clearInitialState();
             }
         }
-        if (_systemEventListenerClassMap != null)
+        if (_systemEventListeners != null)
         {
             for (Map.Entry<Class<? extends SystemEvent>, List<SystemEventListener>> entry : 
-                _systemEventListenerClassMap.entrySet())
+                _systemEventListeners.entrySet())
             {
                 ((PartialStateHolder) entry.getValue()).clearInitialState();
             }
@@ -1186,10 +1186,10 @@ public void markInitialState()
                 ((PartialStateHolder) entry.getValue()).markInitialState();
             }
         }
-        if (_systemEventListenerClassMap != null)
+        if (_systemEventListeners != null)
         {
             for (Map.Entry<Class<? extends SystemEvent>, List<SystemEventListener>> entry : 
-                _systemEventListenerClassMap.entrySet())
+                _systemEventListeners.entrySet())
             {
                 ((PartialStateHolder) entry.getValue()).markInitialState();
             }
@@ -2179,19 +2179,21 @@ private Object saveBehaviorsMap(FacesContext facesContext)
     @SuppressWarnings("unchecked")
     private void restoreFullSystemEventListenerClassMap(FacesContext facesContext, Object stateObj)
     {
+        // All list references are replaced — drop the entire view cache.
+        _unmodifiableSystemEventListeners = null;
         if (stateObj != null)
         {
             Map<Class<? extends SystemEvent>, Object> stateMap = (Map<Class<? extends SystemEvent>, Object>) stateObj;
-            _systemEventListenerClassMap = new HashMap<>(stateMap.size(), 1f);
+            _systemEventListeners = new HashMap<>(stateMap.size(), 1f);
             for (Map.Entry<Class<? extends SystemEvent>, Object> entry : stateMap.entrySet())
             {
-                _systemEventListenerClassMap.put(entry.getKey(),
+                _systemEventListeners.put(entry.getKey(),
                         (List<SystemEventListener>) restoreAttachedState(facesContext, entry.getValue()));
             }
         }
         else
         {
-            _systemEventListenerClassMap = null;
+            _systemEventListeners = null;
         }        
     }
     
@@ -2201,23 +2203,27 @@ private void restoreDeltaSystemEventListenerClassMap(FacesContext facesContext,
         if (stateObj != null)
         {
             Map<Class<? extends SystemEvent>, Object> stateMap = (Map<Class<? extends SystemEvent>, Object>) stateObj;
-            if (_systemEventListenerClassMap == null)
+            if (_systemEventListeners == null)
             {
-                _systemEventListenerClassMap = new HashMap<>(stateMap.size(), 1f);
+                _systemEventListeners = new HashMap<>(stateMap.size(), 1f);
             }
             for (Map.Entry<Class<? extends SystemEvent>, Object> entry : stateMap.entrySet())
             {
                 Object savedObject = entry.getValue(); 
                 if (savedObject instanceof _AttachedDeltaWrapper)
                 {
-                    StateHolder holderList = (StateHolder) _systemEventListenerClassMap.get(entry.getKey());
+                    // In-place delta restore: the existing list reference is kept, its contents
+                    // are mutated. The cached UnmodifiableList view delegates reads to the
+                    // backing list, so no cache invalidation is needed.
+                    StateHolder holderList = (StateHolder) _systemEventListeners.get(entry.getKey());
                     holderList.restoreState(facesContext,
                                             ((_AttachedDeltaWrapper) savedObject).getWrappedStateObject());
                 }
                 else
                 {
-                    _systemEventListenerClassMap.put(entry.getKey(),
+                    _systemEventListeners.put(entry.getKey(),
                             (List<SystemEventListener>) restoreAttachedState(facesContext, savedObject));
+                    _unmodifiableSystemEventListeners = null;
                 }
             }
         }
@@ -2225,15 +2231,15 @@ private void restoreDeltaSystemEventListenerClassMap(FacesContext facesContext,
     
     private Object saveSystemEventListenerClassMap(FacesContext facesContext)
     {
-        if (_systemEventListenerClassMap != null)
+        if (_systemEventListeners != null)
         {
             if (initialStateMarked())
             {
                 HashMap<Class<? extends SystemEvent>, Object> stateMap
-                        = new HashMap<>(_systemEventListenerClassMap.size(), 1);
+                        = new HashMap<>(_systemEventListeners.size(), 1);
                 boolean nullDelta = true;
                 for (Map.Entry<Class<? extends SystemEvent>, List<SystemEventListener> > entry
-                        : _systemEventListenerClassMap.entrySet())
+                        : _systemEventListeners.entrySet())
                 {
                     // The list is always an instance of _DeltaList so we can cast to
                     // PartialStateHolder 
@@ -2244,7 +2250,7 @@ private Object saveSystemEventListenerClassMap(FacesContext facesContext)
                         if (attachedState != null)
                         {
                             stateMap.put(entry.getKey(),
-                                    new _AttachedDeltaWrapper(_systemEventListenerClassMap.getClass(), attachedState));
+                                    new _AttachedDeltaWrapper(_systemEventListeners.getClass(), attachedState));
                             nullDelta = false;
                         }
                     }
@@ -2264,9 +2270,9 @@ private Object saveSystemEventListenerClassMap(FacesContext facesContext)
             {
                 //Save it in the traditional way
                 HashMap<Class<? extends SystemEvent>, Object> stateMap = 
-                    new HashMap<>(_systemEventListenerClassMap.size(), 1);
+                    new HashMap<>(_systemEventListeners.size(), 1);
                 for (Map.Entry<Class<? extends SystemEvent>, List<SystemEventListener>> entry
-                        : _systemEventListenerClassMap.entrySet())
+                        : _systemEventListeners.entrySet())
                 {
                     stateMap.put(entry.getKey(), saveAttachedState(facesContext, entry.getValue()));
                 }


(myfaces) 05/05: fixed build

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git

commit 46ec9b484ce1e0c54323d0f6a02cc2dc1b70a85f
Author: tandraschko <ta...@apache.org>
AuthorDate: Wed Jun 3 19:05:02 2026 +0200

    fixed build
---
 .../renderkit/html/HtmlResponseWriterImpl.java       | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java b/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
index 85d7203f2..cbac7f2fd 100644
--- a/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
@@ -35,7 +35,6 @@
 import jakarta.faces.render.Renderer;
 
 import org.apache.myfaces.config.webparameters.MyfacesConfig;
-import org.apache.myfaces.renderkit.html.util.CommonHtmlEventsUtil;
 import org.apache.myfaces.core.api.shared.ComponentUtils;
 import org.apache.myfaces.renderkit.ContentTypeUtils;
 import org.apache.myfaces.renderkit.html.util.UnicodeEncoder;
@@ -309,7 +308,6 @@ public void startDocument()
     public void endDocument() throws IOException
     {
         FacesContext facesContext = getFacesContext();
-        CommonHtmlEventsUtil.flushDeferredCspBehaviorScripts(facesContext, this);
         MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(facesContext);
         if (myfacesConfig.isEarlyFlushEnabled())
         {
@@ -340,9 +338,9 @@ public void startElement(String name, UIComponent uiComponent) throws IOExceptio
             Object value = _passThroughAttributesMap.get(Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY);
             if (value != null)
             {
-                if (value instanceof ValueExpression expression)
+                if (value instanceof ValueExpression)
                 {
-                    value = expression.getValue(getFacesContext().getELContext());
+                    value = ((ValueExpression) value).getValue(getFacesContext().getELContext());
                 }
                 String elementName = value.toString().trim();
                 
@@ -427,9 +425,9 @@ private void closeStartTagIfNecessary() throws IOException
                     }
                     
                     Object value = entry.getValue();
-                    if (value instanceof ValueExpression expression)
+                    if (value instanceof ValueExpression)
                     {
-                        value = expression.getValue(getFacesContext().getELContext());
+                        value = ((ValueExpression) value).getValue(getFacesContext().getELContext());
                     }
                     // encodeAndWriteURIAttribute(key, value, key);
                     // Faces 2.2 In the renderkit javadoc of jsf 2.2 spec says this 
@@ -790,17 +788,17 @@ public void writeAttribute(String name, Object value, String componentPropertyNa
         // Fast path: the overwhelming majority of attribute values are already Strings,
         // so handle them first and skip both the Boolean instanceof check and the
         // (no-op for String) toString() virtual dispatch.
-        if (value instanceof String strValue)
+        if (value instanceof String)
         {
             _currentWriter.write(' ');
             _currentWriter.write(name);
             _currentWriter.write("=\"");
-            HTMLEncoder.encode(_currentWriter, strValue, false, false, !_isUTF8);
+            HTMLEncoder.encode(_currentWriter, (String) value, false, false, !_isUTF8);
             _currentWriter.write('"');
         }
-        else if (value instanceof Boolean boolean1)
+        else if (value instanceof Boolean)
         {
-            if (boolean1.booleanValue())
+            if ((Boolean) value)
             {
                 // name as value for XHTML compatibility
                 _currentWriter.write(' ');
@@ -890,7 +888,7 @@ public void writeText(Object value, String componentPropertyName) throws IOExcep
 
         closeStartTagIfNecessary();
 
-        String strValue = (value instanceof String s) ? s : value.toString();
+        String strValue = (value instanceof String) ? (String) value : value.toString();
 
         if (isScriptOrStyle())
         {


(myfaces) 04/05: [perf] reuse variables

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git

commit f486f75c42d25121630c31c4c3ada6444f585f66
Author: tandraschko <ta...@apache.org>
AuthorDate: Wed Jun 3 18:56:31 2026 +0200

    [perf] reuse variables
---
 .../faces/component/_ComponentAttributesMap.java   | 26 +++++++++++++---------
 .../jakarta/faces/component/_DeltaStateHelper.java | 16 +++++++------
 .../component/behavior/_DeltaStateHelper.java      |  7 +++---
 3 files changed, 28 insertions(+), 21 deletions(-)

diff --git a/api/src/main/java/jakarta/faces/component/_ComponentAttributesMap.java b/api/src/main/java/jakarta/faces/component/_ComponentAttributesMap.java
index b4e7947cd..328893324 100755
--- a/api/src/main/java/jakarta/faces/component/_ComponentAttributesMap.java
+++ b/api/src/main/java/jakarta/faces/component/_ComponentAttributesMap.java
@@ -27,6 +27,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import jakarta.el.ELContext;
 import jakarta.el.ValueExpression;
 import jakarta.faces.FacesException;
 import jakarta.faces.application.Resource;
@@ -75,7 +76,7 @@ class _ComponentAttributesMap implements Map<String, Object>, Serializable
     public static final String PROPERTY_DESCRIPTOR_MAP_KEY = "oam.cc.beanInfo.PDM";
     
     /**
-     * This variable works as a check to indicate the minimun lenght we need to check
+     * This variable works as a check to indicate the minimum length we need to check
      * for the special attributes, and save some time in get(), containsKey() and 
      * put() operations.
      */
@@ -318,8 +319,11 @@ else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
         }
         else
         {
+            // Hoist the underlying map once; it may be needed up to 3 times below.
+            Map<String, Object> underlyingMap = getUnderlyingMap();
+
             // is there a literal value to read?
-            value = getUnderlyingMap().get(key);
+            value = underlyingMap.get(key);
             if (value == null)
             {
                 // is there a value-binding to read?
@@ -332,15 +336,18 @@ else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
                 {
                     if (!_isCompositeComponentSet)
                     {
-                        _isCompositeComponent = getUnderlyingMap().containsKey(Resource.COMPONENT_RESOURCE_KEY);
+                        _isCompositeComponent = underlyingMap.containsKey(Resource.COMPONENT_RESOURCE_KEY);
                         _isCompositeComponentSet = true;
                     }
                     if (_isCompositeComponent)
                     {
                         BeanInfo ccBeanInfo = _ccBeanInfo != null ? _ccBeanInfo :
-                            (BeanInfo) getUnderlyingMap().get(UIComponent.BEANINFO_KEY);
+                            (BeanInfo) underlyingMap.get(UIComponent.BEANINFO_KEY);
                         if (ccBeanInfo != null)
                         {
+                            // Hoist ELContext once for all composite attribute evaluations below.
+                            ELContext elContext = _component.getFacesContext().getELContext();
+
                             //Fast shortcut to allow fast lookup.
                             Map<String, PropertyDescriptor> attributeMap = (Map<String, PropertyDescriptor>) 
                                 ccBeanInfo.getBeanDescriptor().getValue(
@@ -366,8 +373,7 @@ else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
                                         // Check if the value expression holds a method signature
                                         // Note that it could be null, so in that case we don't have to 
                                         // do anything
-                                        methodSignature = methodSignatureExpression.getValue(
-                                                                    _component.getFacesContext().getELContext());
+                                        methodSignature = methodSignatureExpression.getValue(elContext);
                                     }
 
                                     // either the attributeName has to be a knownMethod
@@ -406,8 +412,7 @@ else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
                                             // Check if the value expression holds a method signature
                                             // Note that it could be null, so in that case we don't have to 
                                             // do anything
-                                            methodSignature = methodSignatureExpression.getValue(
-                                                                        _component.getFacesContext().getELContext());
+                                            methodSignature = methodSignatureExpression.getValue(elContext);
                                         }
 
                                         // either the attributeName has to be a knownMethod
@@ -431,7 +436,7 @@ else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
                             // always stored as (Tag-)ValueExpressions.
                             if (value != null && value instanceof ValueExpression)
                             {
-                                return ((ValueExpression) value).getValue(_component.getFacesContext().getELContext());
+                                return ((ValueExpression) value).getValue(elContext);
                             }
                         }
                     }
@@ -618,7 +623,7 @@ private PropertyDescriptorWrapper getPropertyDescriptor(String key)
         if (_propertyDescriptorMap == null)
         {
             _propertyDescriptorMap = PropertyDescriptorUtils.getCachedPropertyDescriptors(
-                    _component.getFacesContext().getExternalContext(), 
+                    _component.getFacesContext().getExternalContext(),
                     _component.getClass());
         }
         return _propertyDescriptorMap.get(key);
@@ -688,7 +693,6 @@ private void setComponentProperty(PropertyDescriptorWrapper propertyDescriptor,
             BiConsumer<Object, Object> writeFunction = null;
             if (propertyDescriptor instanceof LambdaPropertyDescriptor)
             {
-                ((LambdaPropertyDescriptor) propertyDescriptor).getWriteFunction().accept(_component, value);
                 writeFunction = ((LambdaPropertyDescriptor) propertyDescriptor).getWriteFunction();
             }
 
diff --git a/api/src/main/java/jakarta/faces/component/_DeltaStateHelper.java b/api/src/main/java/jakarta/faces/component/_DeltaStateHelper.java
index 71abb17c1..6c3de608b 100644
--- a/api/src/main/java/jakarta/faces/component/_DeltaStateHelper.java
+++ b/api/src/main/java/jakarta/faces/component/_DeltaStateHelper.java
@@ -623,10 +623,11 @@ public boolean isTransient()
     @Override
     public Object saveState(FacesContext context)
     {
-        Map serializableMap = (isInitialStateMarked()) ? _deltas : _fullState;
+        boolean initialStateMarked = isInitialStateMarked();
+        Map serializableMap = initialStateMarked ? _deltas : _fullState;
 
         if (_initialState != null && _deltas != null && !_deltas.isEmpty()
-            && isInitialStateMarked())
+            && initialStateMarked)
         {
             // Before save the state, check if the property was changed from the
             // initial state value. If the property was changed but it has the
@@ -645,9 +646,9 @@ && isInitialStateMarked())
                 // Check only if there is delta state for that property, in other
                 // case it is not necessary. Remember it is possible to have
                 // null values inside the Map.
-                if (_deltas.containsKey(key))
+                Object deltaValue = _deltas.get(key);
+                if (deltaValue != null || _deltas.containsKey(key))
                 {
-                    Object deltaValue = _deltas.get(key);
                     if (deltaValue == null && defaultValue == null)
                     {
                         _deltas.remove(key);
@@ -713,8 +714,9 @@ public void restoreState(FacesContext context, Object state)
         }
 
         Object[] serializedState = (Object[]) state;
-        
-        if (!isInitialStateMarked() && !_fullState.isEmpty())
+
+        boolean initialStateMarked = isInitialStateMarked();
+        if (!initialStateMarked && !_fullState.isEmpty())
         {
             _fullState.clear();
             if(_deltas != null)
@@ -728,7 +730,7 @@ public void restoreState(FacesContext context, Object state)
             Serializable key = (Serializable) serializedState[cnt];
             Object savedValue = UIComponentBase.restoreAttachedState(context, serializedState[cnt + 1]);
 
-            if (isInitialStateMarked())
+            if (initialStateMarked)
             {
                 if (savedValue instanceof InternalDeltaListMap)
                 {
diff --git a/api/src/main/java/jakarta/faces/component/behavior/_DeltaStateHelper.java b/api/src/main/java/jakarta/faces/component/behavior/_DeltaStateHelper.java
index 393e95689..cd3b885bb 100644
--- a/api/src/main/java/jakarta/faces/component/behavior/_DeltaStateHelper.java
+++ b/api/src/main/java/jakarta/faces/component/behavior/_DeltaStateHelper.java
@@ -561,8 +561,9 @@ public void restoreState(FacesContext context, Object state)
         }
 
         Object[] serializedState = (Object[]) state;
-        
-        if (!isInitialStateMarked() && !_fullState.isEmpty())
+
+        boolean initialStateMarked = isInitialStateMarked();
+        if (!initialStateMarked && !_fullState.isEmpty())
         {
             _fullState.clear();
             if(_deltas != null)
@@ -576,7 +577,7 @@ public void restoreState(FacesContext context, Object state)
             Serializable key = (Serializable) serializedState[cnt];
             Object savedValue = restoreAttachedState(context, serializedState[cnt + 1]);
 
-            if (isInitialStateMarked())
+            if (initialStateMarked)
             {
                 if (savedValue instanceof InternalDeltaListMap)
                 {


(myfaces) 03/05: [perf] avoid new String creation

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch 4.1.x
in repository https://gitbox.apache.org/repos/asf/myfaces.git

commit 5952afa90ef535f7d444243c68077b263c1f6ed5
Author: tandraschko <ta...@apache.org>
AuthorDate: Wed Jun 3 18:48:42 2026 +0200

    [perf] avoid new String creation
---
 .../renderkit/html/HtmlResponseWriterImpl.java     | 179 ++++++++++-----------
 .../renderkit/html/HtmlResponseWriterImplTest.java |  87 ++++++++++
 2 files changed, 170 insertions(+), 96 deletions(-)

diff --git a/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java b/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
index 798e3b477..85d7203f2 100644
--- a/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImpl.java
@@ -35,6 +35,7 @@
 import jakarta.faces.render.Renderer;
 
 import org.apache.myfaces.config.webparameters.MyfacesConfig;
+import org.apache.myfaces.renderkit.html.util.CommonHtmlEventsUtil;
 import org.apache.myfaces.core.api.shared.ComponentUtils;
 import org.apache.myfaces.renderkit.ContentTypeUtils;
 import org.apache.myfaces.renderkit.html.util.UnicodeEncoder;
@@ -65,6 +66,8 @@ public class HtmlResponseWriterImpl extends ResponseWriter
     private static final String COMMENT_COMMENT_END = "\n//-->";
     private static final String COMMENT_END = "\n-->";
 
+    private static final String JAVASCRIPT_SCHEME = "javascript:";
+
     static private final String[][] EMPTY_ELEMENT_ARR = new String[256][];
 
     static private final String[] A_NAMES = new String[]
@@ -183,7 +186,7 @@ public class HtmlResponseWriterImpl extends ResponseWriter
     private String _startElementName;
     private boolean _isInsideScript = false;
     private boolean _isStyle = false;
-    private Boolean _isTextArea;
+    private boolean _isTextArea = false;
     private UIComponent _startElementUIComponent;
     private boolean _startTagOpen;
     private Map<String, Object> _passThroughAttributesMap;
@@ -306,6 +309,7 @@ public void startDocument()
     public void endDocument() throws IOException
     {
         FacesContext facesContext = getFacesContext();
+        CommonHtmlEventsUtil.flushDeferredCspBehaviorScripts(facesContext, this);
         MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(facesContext);
         if (myfacesConfig.isEarlyFlushEnabled())
         {
@@ -336,9 +340,9 @@ public void startElement(String name, UIComponent uiComponent) throws IOExceptio
             Object value = _passThroughAttributesMap.get(Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY);
             if (value != null)
             {
-                if (value instanceof ValueExpression)
+                if (value instanceof ValueExpression expression)
                 {
-                    value = ((ValueExpression)value).getValue(getFacesContext().getELContext());
+                    value = expression.getValue(getFacesContext().getELContext());
                 }
                 String elementName = value.toString().trim();
                 
@@ -366,21 +370,23 @@ public void startElement(String name, UIComponent uiComponent) throws IOExceptio
             _startedElementsCount.set(i, _startedElementsCount.get(i)+1);
         }
         
-        // Each time we start a element, it is necessary to check <script> or <style>,
-        // because we need to buffer all content to post process it later when it reach its end
-        // according to the initialization properties used.
-        if(isScript(_startElementName))
+        // Each time we start an element, check for <script>, <style>, or <textarea>
+        // so that writeText() can make the right encoding choice without lazy detection.
+        if (isScript(_startElementName))
         {
-            // handle a <script> start
             _isInsideScript = true;
             _isStyle = false;
-            _isTextArea = Boolean.FALSE;
+            _isTextArea = false;
         }
         else if (isStyle(_startElementName))
         {
             _isInsideScript = false;
             _isStyle = true;
-            _isTextArea = Boolean.FALSE;
+            _isTextArea = false;
+        }
+        else
+        {
+            _isTextArea = HTML.TEXTAREA_ELEM.equalsIgnoreCase(_startElementName);
         }
     }
 
@@ -421,9 +427,9 @@ private void closeStartTagIfNecessary() throws IOException
                     }
                     
                     Object value = entry.getValue();
-                    if (value instanceof ValueExpression)
+                    if (value instanceof ValueExpression expression)
                     {
-                        value = ((ValueExpression)value).getValue(getFacesContext().getELContext());
+                        value = expression.getValue(getFacesContext().getELContext());
                     }
                     // encodeAndWriteURIAttribute(key, value, key);
                     // Faces 2.2 In the renderkit javadoc of jsf 2.2 spec says this 
@@ -437,7 +443,7 @@ private void closeStartTagIfNecessary() throws IOException
                     // Note here it says "as if they were passed", instead say "... attributes are
                     // encoded and rendered as if ...". Black box testing against RI shows that there
                     // is no URI encoding at all in this part, so in this case the best is do the
-                    // same here. After all, it is resposibility of the one who set the passthrough
+                    // same here. After all, it is responsibility of the one who set the passthrough
                     // attribute to do the proper encoding in cases when a URI is provided. However,
                     // that does not means the attribute should not be encoded as other attributes.
                     encodeAndWriteAttribute(key, value);
@@ -495,7 +501,7 @@ private void resetStartedElement()
         _startElementUIComponent = null;
         _passThroughAttributesMap = null;
         _isStyle = false;
-        _isTextArea = null;
+        _isTextArea = false;
     }
 
     @Override
@@ -507,9 +513,10 @@ public void endElement(String name) throws IOException
 
         if (!_startedElementsCount.isEmpty())
         {
-            int i = _startedElementsCount.size()-1;
-            _startedElementsCount.set(i, _startedElementsCount.get(i)-1);
-            if (_startedElementsCount.get(i) == 0)
+            int i = _startedElementsCount.size() - 1;
+            int count = _startedElementsCount.get(i) - 1;
+            _startedElementsCount.set(i, count);
+            if (count == 0)
             {
                 elementName = _startedChangedElements.get(i);
                 _startedChangedElements.remove(i);
@@ -582,9 +589,9 @@ else if (isStyle() && _isXhtmlContentType)
     
     private void writeStyleContent() throws IOException
     {
-        String content = getInternalBuffer().toString();
-        
-        if(_isXhtmlContentType)
+        StreamCharBuffer buffer = getInternalBuffer();
+
+        if (_isXhtmlContentType)
         {
             // In xhtml, the content inside <style> tag is PCDATA, but
             // in html the content is CDATA, so in order to preserve 
@@ -592,7 +599,9 @@ private void writeStyleContent() throws IOException
             // CDATA tags.
             // Since the response content type is xhtml, we can use
             // simple CDATA without comments, but note we need to check
-            // when we are using any valid notation (simple CDATA, commented CDATA, xml comment) 
+            // when we are using any valid notation (simple CDATA, commented CDATA, xml comment)
+            // toString() is only called here because we need trim()/startsWith() inspection.
+            String content = buffer.toString();
             String trimmedContent = content.trim();
             if (trimmedContent.startsWith(CommentUtils.CDATA_SIMPLE_START) && trimmedContent.endsWith(
                     CommentUtils.CDATA_SIMPLE_END))
@@ -618,27 +627,28 @@ else if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
             else
             {
                 _outputWriter.write(CDATA_START);
-                _outputWriter.write(content);
+                buffer.writeTo(_outputWriter);
                 _outputWriter.write(CDATA_END);
                 return;                
             }
         }
-        // If the response is handled as text/html, 
-        // it is not necessary to wrap with xml comment tag,
-        // so we can just write the content as is.
-        _outputWriter.write(content);
+        // If the response is handled as text/html,
+        // it is not necessary to wrap with xml comment tag, so write the buffer directly
+        // without materialising it to a String first.
+        buffer.writeTo(_outputWriter);
     }
     
     private void writeScriptContent() throws IOException
     {
-        String content = getInternalBuffer().toString();
-        String trimmedContent = null;
-        
-        if(_isXhtmlContentType)
+        StreamCharBuffer buffer = getInternalBuffer();
+
+        if (_isXhtmlContentType)
         {
-            trimmedContent = content.trim();
+            // toString() needed here for trim()/startsWith() inspection
+            String content = buffer.toString();
+            String trimmedContent = content.trim();
             
-            if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
+            if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
                     CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
             {
                 // In xhtml use xml comment to wrap is invalid, so it is only required to remove the <!--
@@ -690,7 +700,7 @@ else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) &&
                    _outputWriter.write(CDATA_COMMENT_START);
                 }
 
-                _outputWriter.write(content);
+                buffer.writeTo(_outputWriter);
 
                 if (_cdataOpen)
                 {
@@ -704,42 +714,42 @@ else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) &&
                 return;
             }
         }
-        else
+        else if (_wrapScriptContentWithXmlCommentTag)
         {
-            if (_wrapScriptContentWithXmlCommentTag)
+            // toString() needed here for trim()/startsWith() inspection
+            String content = buffer.toString();
+            String trimmedContent = content.trim();
+
+            if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
+                    CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
             {
-                trimmedContent = content.trim();
-                
-                if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
-                        CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
-                {
-                    _outputWriter.write(content);
-                    return;
-                }
-                else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
-                        CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
-                {
-                    _outputWriter.write(content);
-                    return;
-                }
-                else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) && 
-                        CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
-                {
-                    _outputWriter.write(content);
-                    return;
-                }
-                else
-                {
-                    _outputWriter.write(COMMENT_START);
-                    _outputWriter.write(content);
-                    _outputWriter.write(COMMENT_COMMENT_END);
-                    return;
-                }
+                _outputWriter.write(content);
+                return;
+            }
+            else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) &&
+                    CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
+            {
+                _outputWriter.write(content);
+                return;
+            }
+            else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) &&
+                    CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
+            {
+                _outputWriter.write(content);
+                return;
+            }
+            else
+            {
+                _outputWriter.write(COMMENT_START);
+                buffer.writeTo(_outputWriter);
+                _outputWriter.write(COMMENT_COMMENT_END);
+                return;
             }
         }
-        
-        //If no return, just write everything
-        _outputWriter.write(content);
+
+        // Common path: plain HTML without script wrapping — write buffer directly,
+        // avoiding a full String materialisation of the script content.
+        buffer.writeTo(_outputWriter);
     }
     
 
@@ -780,18 +790,17 @@ public void writeAttribute(String name, Object value, String componentPropertyNa
         // Fast path: the overwhelming majority of attribute values are already Strings,
         // so handle them first and skip both the Boolean instanceof check and the
         // (no-op for String) toString() virtual dispatch.
-        if (value instanceof String)
+        if (value instanceof String strValue)
         {
-            String strValue = (String) value;
             _currentWriter.write(' ');
             _currentWriter.write(name);
             _currentWriter.write("=\"");
             HTMLEncoder.encode(_currentWriter, strValue, false, false, !_isUTF8);
             _currentWriter.write('"');
         }
-        else if (value instanceof Boolean)
+        else if (value instanceof Boolean boolean1)
         {
-            if (((Boolean) value).booleanValue())
+            if (boolean1.booleanValue())
             {
                 // name as value for XHTML compatibility
                 _currentWriter.write(' ');
@@ -852,7 +861,7 @@ private void encodeAndWriteURIAttribute(String name, Object value) throws IOExce
         _currentWriter.write(' ');
         _currentWriter.write(name);
         _currentWriter.write("=\"");
-        if (strValue.toLowerCase().startsWith("javascript:"))
+        if (strValue.regionMatches(true, 0, JAVASCRIPT_SCHEME, 0, JAVASCRIPT_SCHEME.length()))
         {
             HTMLEncoder.encode(_currentWriter, strValue, false, false, !_isUTF8);
         }
@@ -881,8 +890,7 @@ public void writeText(Object value, String componentPropertyName) throws IOExcep
 
         closeStartTagIfNecessary();
 
-        // Avoid a virtual dispatch when the value is already a String (the common case).
-        String strValue = value instanceof String ? (String) value : value.toString();
+        String strValue = (value instanceof String s) ? s : value.toString();
 
         if (isScriptOrStyle())
         {
@@ -916,15 +924,14 @@ public void writeText(char[] cbuf, int off, int len) throws IOException
 
         if (isScriptOrStyle())
         {
-            String strValue = new String(cbuf, off, len);
             // Don't bother encoding anything if chosen character encoding is UTF-8
             if (_isUTF8)
             {
-                _currentWriter.write(strValue);
+                _currentWriter.write(cbuf, off, len);
             }
             else
             {
-                UnicodeEncoder.encode(_currentWriter, strValue);
+                UnicodeEncoder.encode(_currentWriter, cbuf, off, len);
             }
         }
         else if (isTextarea())
@@ -971,27 +978,7 @@ private boolean isStyle()
 
     private boolean isTextarea()
     {
-        initializeStartedTagInfo();
-
-        return _isTextArea != null && _isTextArea;
-    }
-
-    private void initializeStartedTagInfo()
-    {
-        if (_startElementName != null)
-        {
-            if (_isTextArea == null)
-            {
-                if (_startElementName.equalsIgnoreCase(HTML.TEXTAREA_ELEM))
-                {
-                    _isTextArea = Boolean.TRUE;
-                }
-                else
-                {
-                    _isTextArea = Boolean.FALSE;
-                }
-            }
-        }
+        return _isTextArea;
     }
 
     @Override
diff --git a/impl/src/test/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImplTest.java b/impl/src/test/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImplTest.java
index 27103ac9c..821a223f1 100644
--- a/impl/src/test/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImplTest.java
+++ b/impl/src/test/java/org/apache/myfaces/renderkit/html/HtmlResponseWriterImplTest.java
@@ -291,6 +291,93 @@ public void testStyleOnXhtmlUTF8Encoding() throws IOException
         Assertions.assertTrue(output.contains(CommentUtils.CDATA_SIMPLE_END), "script does not have end ]]> ");
     }
     
+    /**
+     * writeText(char[]) inside a textarea must NOT map successive spaces to &amp;nbsp;
+     * or newlines to &lt;br/&gt; — this is the key purpose of the _isTextArea flag.
+     */
+    @Test
+    public void testTextareaWriteTextPreservesSpacesAndNewlines() throws IOException
+    {
+        _writer.startDocument();
+        _writer.startElement("form", null);
+        _writer.startElement(HTML.TEXTAREA_ELEM, null);
+        _writer.writeText("hello   world\nfoo".toCharArray(), 0, 17);
+        _writer.endElement(HTML.TEXTAREA_ELEM);
+        _writer.endElement("form");
+        _writer.endDocument();
+
+        String output = _stringWriter.toString();
+        // Spaces must not be turned into &nbsp; inside a textarea
+        Assertions.assertFalse(output.contains("&nbsp;"),
+                "textarea: spaces must not be encoded as &nbsp;");
+        // Newlines must not be turned into <br/> inside a textarea
+        Assertions.assertFalse(output.contains("<br"),
+                "textarea: newlines must not be encoded as <br/>");
+        // The original characters must appear verbatim
+        Assertions.assertTrue(output.contains("hello   world"),
+                "textarea: original spaces must be present in output");
+    }
+
+    /**
+     * Same as testTextareaWriteTextPreservesSpacesAndNewlines but with uppercase element name
+     * to verify case-insensitive textarea detection.
+     */
+    @Test
+    public void testTextareaWriteTextUppercase() throws IOException
+    {
+        _writer.startDocument();
+        _writer.startElement("form", null);
+        _writer.startElement("TEXTAREA", null);
+        _writer.writeText("hello   world".toCharArray(), 0, 13);
+        _writer.endElement("TEXTAREA");
+        _writer.endElement("form");
+        _writer.endDocument();
+
+        String output = _stringWriter.toString();
+        Assertions.assertFalse(output.contains("&nbsp;"),
+                "TEXTAREA (uppercase): spaces must not be encoded as &nbsp;");
+    }
+
+    /**
+     * writeText(Object) with a non-String value must produce the same output as
+     * writeText(Object) with the equivalent String — exercises the toString() fallback path.
+     */
+    @Test
+    public void testWriteTextObjectNonString() throws IOException
+    {
+        _writer.startDocument();
+        _writer.startElement("body", null);
+        _writer.writeText(Integer.valueOf(42), null);
+        _writer.endElement("body");
+        _writer.endDocument();
+
+        Assertions.assertTrue(_stringWriter.toString().contains("42"),
+                "Non-String value (Integer 42) must be rendered as its toString()");
+    }
+
+    /**
+     * Style content on plain text/html must be written verbatim without toString() overhead
+     * (exercises the StreamCharBuffer.writeTo path in writeStyleContent).
+     */
+    @Test
+    public void testStyleOnHtmlWritesContentVerbatim() throws IOException
+    {
+        _writer = new HtmlResponseWriterImpl(_stringWriter, "text/html", "UTF-8", false);
+        String css = "body { color: red; }";
+        _writer.startDocument();
+        _writer.startElement(HTML.STYLE_ELEM, null);
+        _writer.write(css);
+        _writer.endElement(HTML.STYLE_ELEM);
+        _writer.endDocument();
+
+        String output = _stringWriter.toString();
+        Assertions.assertTrue(output.contains(css),
+                "Plain HTML style content must appear verbatim in output");
+        // In plain text/html there is no CDATA wrapping
+        Assertions.assertFalse(output.contains(CommentUtils.CDATA_SIMPLE_START),
+                "Plain HTML style must not be wrapped in CDATA");
+    }
+
     /**
      * In html, it is not valid to have an empty tag with content
      *