You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by mm...@apache.org on 2007/10/26 11:19:57 UTC

svn commit: r588578 - in /myfaces: core/branches/1_2_1/api/src/main/java/javax/faces/component/ core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implici...

Author: mmarinschek
Date: Fri Oct 26 02:19:56 2007
New Revision: 588578

URL: http://svn.apache.org/viewvc?rev=588578&view=rev
Log:
https://issues.apache.org/jira/browse/MYFACES-1749 (MYFACES-1749): Performance Improvements. Thanks to Michael Kurz.

Added:
    myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/
    myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/
    myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/
    myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoderTest.java
Modified:
    myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/UIComponentBase.java
    myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/_ComponentAttributesMap.java
    myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java
    myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implicitobject/ImplicitObjectResolver.java
    myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlResponseWriterImpl.java
    myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoder.java
    myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/UnicodeEncoder.java

Modified: myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/UIComponentBase.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/UIComponentBase.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/UIComponentBase.java (original)
+++ myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/UIComponentBase.java Fri Oct 26 02:19:56 2007
@@ -1078,20 +1078,23 @@
             throw new IllegalArgumentException("component identifier must not be a zero-length String");
         }
 
-        //let's look at all chars inside of the ID if it is a valid ID!
-        char[] chars = string.toCharArray();
-
+        // If new id is the same as old it must be valid
+        if (string.equals(_id)) {
+        	return;
+        }
+        
         //2. First character must be a letter or an underscore ('_').
-        if(!Character.isLetter(chars[0]) &&  chars[0] !='_')
+        if(!Character.isLetter(string.charAt(0)) &&  string.charAt(0) !='_')
         {
-            throw new IllegalArgumentException("component identifier's first character must be a letter or an underscore ('_')! But it is \""+chars[0]+"\"");
+            throw new IllegalArgumentException("component identifier's first character must be a letter or an underscore ('_')! But it is \""+string.charAt(0)+"\"");
         }
-        for (int i = 1; i < chars.length; i++)
+        for (int i = 1; i < string.length(); i++)
         {
+        	char c = string.charAt(i);
             //3. Subsequent characters must be a letter, a digit, an underscore ('_'), or a dash ('-').
-            if(!Character.isDigit(chars[i]) && !Character.isLetter(chars[i]) && chars[i] !='-' && chars[i] !='_')
+            if(!Character.isLetterOrDigit(c) && c !='-' && c !='_')
             {
-                throw new IllegalArgumentException("Subsequent characters of component identifier must be a letter, a digit, an underscore ('_'), or a dash ('-')! But component identifier contains \""+chars[i]+"\"");
+                throw new IllegalArgumentException("Subsequent characters of component identifier must be a letter, a digit, an underscore ('_'), or a dash ('-')! But component identifier contains \""+c+"\"");
             }
         }
     }

Modified: myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/_ComponentAttributesMap.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/_ComponentAttributesMap.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/_ComponentAttributesMap.java (original)
+++ myfaces/core/branches/1_2_1/api/src/main/java/javax/faces/component/_ComponentAttributesMap.java Fri Oct 26 02:19:56 2007
@@ -26,6 +26,7 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.WeakHashMap;
 
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
@@ -70,6 +71,9 @@
     // it can always be recreated when needed.
     private transient Map<String, PropertyDescriptor> _propertyDescriptorMap = null;
 
+    // Cache for component property descriptors
+    private static Map<Class, Map<String, PropertyDescriptor>> _propertyDescriptorCache = new WeakHashMap<Class, Map<String,PropertyDescriptor>>();
+
     /**
      * Create a map backed by the specified component.
      * <p>
@@ -330,27 +334,36 @@
     {
         if (_propertyDescriptorMap == null)
         {
-            BeanInfo beanInfo;
-            try
-            {
-                beanInfo = Introspector.getBeanInfo(_component.getClass());
-            }
-            catch (IntrospectionException e)
-            {
-                throw new FacesException(e);
-            }
-            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
-            _propertyDescriptorMap = new HashMap<String, PropertyDescriptor>();
-            for (int i = 0; i < propertyDescriptors.length; i++)
-            {
-                PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
-                if (propertyDescriptor.getReadMethod() != null)
+        	// Try to get descriptor map from cache
+        	_propertyDescriptorMap = _propertyDescriptorCache.get(_component.getClass());
+        	// Cache miss: create descriptor map and put it in cache
+        	if (_propertyDescriptorMap == null)
+        	{
+        		// Create descriptor map...
+                BeanInfo beanInfo;
+                try
                 {
-                    _propertyDescriptorMap.put(propertyDescriptor.getName(),
-                                               propertyDescriptor);
+                    beanInfo = Introspector.getBeanInfo(_component.getClass());
                 }
-            }
-        }
+                catch (IntrospectionException e)
+                {
+                    throw new FacesException(e);
+                }
+                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+                _propertyDescriptorMap = new HashMap<String, PropertyDescriptor>();
+                for (int i = 0; i < propertyDescriptors.length; i++)
+                {
+                    PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
+                    if (propertyDescriptor.getReadMethod() != null)
+                    {
+                        _propertyDescriptorMap.put(propertyDescriptor.getName(),
+                                                   propertyDescriptor);
+                    }
+                }
+                // ... and put it in cache
+                _propertyDescriptorCache.put(_component.getClass(), _propertyDescriptorMap);
+        	}
+    	}
         return _propertyDescriptorMap.get(key);
     }
 

Modified: myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java (original)
+++ myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java Fri Oct 26 02:19:56 2007
@@ -19,11 +19,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.MissingResourceException;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.el.CompositeELResolver;
 import javax.el.ELContext;
@@ -106,14 +106,12 @@
     private ArrayList<ELContextListener> _elContextListeners;
 
     // components, converters, and validators can be added at runtime--must
-    // synchronize
-    private final Map<String, Class> _converterIdToClassMap = Collections.synchronizedMap(new HashMap<String, Class>());
-    private final Map<Class, String> _converterClassNameToClassMap = Collections
-            .synchronizedMap(new HashMap<Class, String>());
-    private final Map<String, org.apache.myfaces.config.impl.digester.elements.Converter> _converterClassNameToConfigurationMap = Collections
-            .synchronizedMap(new HashMap<String, org.apache.myfaces.config.impl.digester.elements.Converter>());
-    private final Map<String, Class> _componentClassMap = Collections.synchronizedMap(new HashMap<String, Class>());
-    private final Map<String, Class> _validatorClassMap = Collections.synchronizedMap(new HashMap<String, Class>());
+    // synchronize, uses ConcurrentHashMap to allow concurrent read of map
+    private final Map<String, Class> _converterIdToClassMap = new ConcurrentHashMap<String, Class>();
+    private final Map<Class, String> _converterClassNameToClassMap = new ConcurrentHashMap<Class, String>();
+    private final Map<String, org.apache.myfaces.config.impl.digester.elements.Converter> _converterClassNameToConfigurationMap = new ConcurrentHashMap<String, org.apache.myfaces.config.impl.digester.elements.Converter>();
+    private final Map<String, Class> _componentClassMap = new ConcurrentHashMap<String, Class>();
+    private final Map<String, Class> _validatorClassMap = new ConcurrentHashMap<String, Class>();
 
     private final RuntimeConfig _runtimeConfig;
 
@@ -627,11 +625,7 @@
         checkNull(componentType, "componentType");
         checkEmpty(componentType, "componentType");
 
-        Class componentClass;
-        synchronized (_componentClassMap)
-        {
-            componentClass = _componentClassMap.get(componentType);
-        }
+        Class componentClass = _componentClassMap.get(componentType);
         if (componentClass == null)
         {
             log.error("Undefined component type " + componentType);

Modified: myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implicitobject/ImplicitObjectResolver.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implicitobject/ImplicitObjectResolver.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implicitobject/ImplicitObjectResolver.java (original)
+++ myfaces/core/branches/1_2_1/impl/src/main/java/org/apache/myfaces/el/unified/resolver/implicitobject/ImplicitObjectResolver.java Fri Oct 26 02:19:56 2007
@@ -19,8 +19,9 @@
 import javax.el.*;
 import java.beans.FeatureDescriptor;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
+import java.util.Map;
 
 /**
  * See JSF 1.2 spec sections 5.6.1.1 and 5.6.2.1
@@ -29,16 +30,18 @@
  */
 public class ImplicitObjectResolver extends ELResolver {
     
-    private List<ImplicitObject> implicitObjects;
+    private Map<String, ImplicitObject> implicitObjects;
     
     /**
      * Static factory for an ELResolver for resolving implicit objects in JSPs. 
      * See JSF 1.2 spec section 5.6.1.1
      */
     public static ELResolver makeResolverForJSP() {
-        List<ImplicitObject> forJSPList = new ArrayList<ImplicitObject>(2);
-        forJSPList.add(new FacesContextImplicitObject());
-        forJSPList.add(new ViewImplicitObject());
+        Map<String, ImplicitObject> forJSPList = new HashMap<String, ImplicitObject>(2);
+        ImplicitObject io1 = new FacesContextImplicitObject();
+        forJSPList.put(io1.getName(), io1);
+        ImplicitObject io2 = new ViewImplicitObject();
+        forJSPList.put(io2.getName(), io2);
         return new ImplicitObjectResolver(forJSPList);
     }
     
@@ -47,32 +50,46 @@
      * See JSF 1.2 spec section 5.6.1.2
      */
     public static ELResolver makeResolverForFaces() {
-        List<ImplicitObject> forFacesList = new ArrayList<ImplicitObject>(14);
-        forFacesList.add(new ApplicationImplicitObject());
-        forFacesList.add(new ApplicationScopeImplicitObject());
-        forFacesList.add(new CookieImplicitObject());
-        forFacesList.add(new FacesContextImplicitObject());
-        forFacesList.add(new HeaderImplicitObject());
-        forFacesList.add(new HeaderValuesImplicitObject());
-        forFacesList.add(new InitParamImplicitObject());
-        forFacesList.add(new ParamImplicitObject());
-        forFacesList.add(new ParamValuesImplicitObject());
-        forFacesList.add(new RequestImplicitObject());
-        forFacesList.add(new RequestScopeImplicitObject());
-        forFacesList.add(new SessionImplicitObject());
-        forFacesList.add(new SessionScopeImplicitObject());
-        forFacesList.add(new ViewImplicitObject());
+        Map<String, ImplicitObject> forFacesList = new HashMap<String, ImplicitObject>(14);
+        ImplicitObject io1 = new ApplicationImplicitObject();
+        forFacesList.put(io1.getName(), io1);
+        ImplicitObject io2 = new ApplicationScopeImplicitObject();
+        forFacesList.put(io2.getName(), io2);
+        ImplicitObject io3 = new CookieImplicitObject();
+        forFacesList.put(io3.getName(), io3);
+        ImplicitObject io4 = new FacesContextImplicitObject();
+        forFacesList.put(io4.getName(), io4);
+        ImplicitObject io5 = new HeaderImplicitObject();
+        forFacesList.put(io5.getName(), io5);
+        ImplicitObject io6 = new HeaderValuesImplicitObject();
+        forFacesList.put(io6.getName(), io6);
+        ImplicitObject io7 = new InitParamImplicitObject();
+        forFacesList.put(io7.getName(), io7);
+        ImplicitObject io8 = new ParamImplicitObject();
+        forFacesList.put(io8.getName(), io8);
+        ImplicitObject io9 = new ParamValuesImplicitObject();
+        forFacesList.put(io9.getName(), io9);
+        ImplicitObject io10 = new RequestImplicitObject();
+        forFacesList.put(io10.getName(), io10);
+        ImplicitObject io11 = new RequestScopeImplicitObject();
+        forFacesList.put(io11.getName(), io11);
+        ImplicitObject io12 = new SessionImplicitObject();
+        forFacesList.put(io12.getName(), io12);
+        ImplicitObject io13 = new SessionScopeImplicitObject();
+        forFacesList.put(io13.getName(), io13);
+        ImplicitObject io14 = new ViewImplicitObject();
+        forFacesList.put(io14.getName(), io14);
         return new ImplicitObjectResolver(forFacesList);        
     }
     
     
     private ImplicitObjectResolver() {
         super();
-        this.implicitObjects = new ArrayList<ImplicitObject>();
+        this.implicitObjects = new HashMap<String, ImplicitObject>();
     }
     
     /** Creates a new instance of ImplicitObjectResolverForJSP */
-    private ImplicitObjectResolver(List<ImplicitObject> implicitObjects) {
+    private ImplicitObjectResolver(Map<String, ImplicitObject> implicitObjects) {
         this();
         this.implicitObjects = implicitObjects;
     }
@@ -86,10 +103,8 @@
         
         String strProperty = castAndIntern(property);
         
-        for (ImplicitObject obj: implicitObjects) {
-            if (strProperty.equals(obj.getName())) {
-                throw new PropertyNotWritableException();
-            }
+        if (implicitObjects.containsKey(strProperty)) {
+            throw new PropertyNotWritableException();
         }
     }
     
@@ -102,11 +117,9 @@
         
         String strProperty = castAndIntern(property);
         
-        for (ImplicitObject obj: implicitObjects) {
-            if (strProperty.equals(obj.getName())) {
-                context.setPropertyResolved(true);
-                return true;
-            }
+        if (implicitObjects.containsKey(strProperty)) {
+            context.setPropertyResolved(true);
+            return true;
         }
         
         return false;
@@ -121,11 +134,10 @@
             
         String strProperty = castAndIntern(property);
 
-        for (ImplicitObject obj : implicitObjects) {
-            if (strProperty.equals(obj.getName())) {
-                context.setPropertyResolved(true);
-                return obj.getValue(context);
-            }
+        ImplicitObject obj = implicitObjects.get(strProperty);
+        if (obj != null) {
+            context.setPropertyResolved(true);
+            return obj.getValue(context);
         }
         
         return null;
@@ -140,10 +152,8 @@
         
         String strProperty = castAndIntern(property);
         
-        for (ImplicitObject obj: implicitObjects) {
-            if (strProperty.equals(obj.getName())) {
-                context.setPropertyResolved(true);
-            }
+        if (implicitObjects.containsKey(strProperty)) {
+            context.setPropertyResolved(true);
         }
         
         return null;
@@ -154,7 +164,7 @@
 
         ArrayList<FeatureDescriptor> descriptors = new ArrayList<FeatureDescriptor>(implicitObjects.size());
         
-        for (ImplicitObject obj: implicitObjects) {
+        for (ImplicitObject obj: implicitObjects.values()) {
             descriptors.add(obj.getDescriptor());
          }
         

Modified: myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlResponseWriterImpl.java
URL: http://svn.apache.org/viewvc/myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlResponseWriterImpl.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlResponseWriterImpl.java (original)
+++ myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlResponseWriterImpl.java Fri Oct 26 02:19:56 2007
@@ -449,16 +449,12 @@
         else if (isTextarea())
         {
             // For textareas we must *not* map successive spaces to &nbsp or Newlines to <br/>
-            // TODO: Make HTMLEncoder support char arrays directly
-            String strValue = new String(cbuf, off, len);
-            _writer.write(org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(strValue, false, false, !UTF8.equals(_characterEncoding)));
+            org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(cbuf, off, len, false, false, !UTF8.equals(_characterEncoding), _writer);
         }
         else
         {
             // We map successive spaces to &nbsp; and Newlines to <br/>
-            // TODO: Make HTMLEncoder support char arrays directly
-            String strValue = new String(cbuf, off, len);
-            _writer.write(org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(strValue, true, true, !UTF8.equals(_characterEncoding)));
+        	org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(cbuf, off, len, true, true, !UTF8.equals(_characterEncoding), _writer);
         }
     }
 

Modified: myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoder.java
URL: http://svn.apache.org/viewvc/myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoder.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoder.java (original)
+++ myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoder.java Fri Oct 26 02:19:56 2007
@@ -18,6 +18,9 @@
  */
 package org.apache.myfaces.shared.renderkit.html.util;
 
+import java.io.IOException;
+import java.io.Writer;
+
 /**
  * Converts Strings so that they can be used within HTML-Code.
  */
@@ -64,68 +67,67 @@
 			return "";
 		}
 
-		StringBuffer sb = null;	//create later on demand
+		StringBuilder sb = null;	//create later on demand
 		String app;
 		char c;
 		for (int i = 0; i < string.length (); ++i)
 		{
 			app = null;
 			c = string.charAt(i);
-			switch (c)
+			
+			// All characters before letters
+			if ((int)c < 0x41)
 			{
-                case '"': app = "&quot;"; break;    //"
-                case '&': app = "&amp;"; break;     //&
-                case '<': app = "&lt;"; break;      //<
-                case '>': app = "&gt;"; break;      //>
-                case ' ':
-                    if (encodeSubsequentBlanksToNbsp &&
-                        (i == 0 || (i - 1 >= 0 && string.charAt(i - 1) == ' ')))
-                    {
-                        //Space at beginning or after another space
-                        app = "&#160;";
-                    }
-                    break;
-                case '\n':
-                    if (encodeNewline)
-                    {
-                        app = "<br/>";
-                    }
-                    break;
-
-
-                default:
-                	if (encodeNonLatin) switch(c) {
-	                	//german umlauts
-					    case '\u00E4' : app = "&auml;";  break;
-					    case '\u00C4' : app = "&Auml;";  break;
-					    case '\u00F6' : app = "&ouml;";  break;
-					    case '\u00D6' : app = "&Ouml;";  break;
-					    case '\u00FC' : app = "&uuml;";  break;
-					    case '\u00DC' : app = "&Uuml;";  break;
-					    case '\u00DF' : app = "&szlig;"; break;
-		
-		                //misc
-		                //case 0x80: app = "&euro;"; break;  sometimes euro symbol is ascii 128, should we suport it?
-		                case '\u20AC': app = "&euro;";  break;
-		                case '\u00AB': app = "&laquo;"; break;
-		                case '\u00BB': app = "&raquo;"; break;
-		                case '\u00A0': app = "&#160;"; break;
-                	
-		                default :
-	                    if (((int)c) >= 0x80)
-	                    {
-	                        //encode all non basic latin characters
-	                        app = "&#" + ((int)c) + ";";
-	                    }
-	                    break;
-                	}
-                	break;
+				switch (c)
+				{
+					case '"': app = "&quot;"; break;    //"
+					case '&': app = "&amp;"; break;     //&
+					case '<': app = "&lt;"; break;      //<
+					case '>': app = "&gt;"; break;      //>
+					case ' ':
+						if (encodeSubsequentBlanksToNbsp &&
+								(i == 0 || (i - 1 >= 0 && string.charAt(i - 1) == ' ')))
+						{
+							//Space at beginning or after another space
+							app = "&#160;";
+						}
+						break;
+					case '\n':
+						if (encodeNewline)
+						{
+							app = "<br/>";
+						}
+						break;
+				}
+			} else if (encodeNonLatin && (int)c > 0x80) {
+				 switch(c) {
+					//german umlauts
+					case '\u00E4' : app = "&auml;";  break;
+					case '\u00C4' : app = "&Auml;";  break;
+					case '\u00F6' : app = "&ouml;";  break;
+					case '\u00D6' : app = "&Ouml;";  break;
+					case '\u00FC' : app = "&uuml;";  break;
+					case '\u00DC' : app = "&Uuml;";  break;
+					case '\u00DF' : app = "&szlig;"; break;
+
+					//misc
+					//case 0x80: app = "&euro;"; break;  sometimes euro symbol is ascii 128, should we suport it?
+					case '\u20AC': app = "&euro;";  break;
+					case '\u00AB': app = "&laquo;"; break;
+					case '\u00BB': app = "&raquo;"; break;
+					case '\u00A0': app = "&#160;"; break;
+
+					default :
+						//encode all non basic latin characters
+						app = "&#" + ((int)c) + ";";
+					break;
+				}
 			}
 			if (app != null)
 			{
 				if (sb == null)
 				{
-					sb = new StringBuffer(string.substring(0, i));
+					sb = new StringBuilder(string.substring(0, i));
 				}
 				sb.append(app);
 			} else {
@@ -146,5 +148,130 @@
 		}
 	}
 
+	/**
+	 * Variant of {@link #encode} where encodeNewline is false and encodeNbsp is true.
+	 */
+	public static void encode (char[] string, int offset, int length, Writer writer) throws IOException
+	{
+		encode(string, offset, length, false, true, writer);
+	}
+
+	/**
+	 * Variant of {@link #encode} where encodeNbsp is true.
+	 */
+	public static void encode (char[] string, int offset, int length, boolean encodeNewline, Writer writer) throws IOException
+	{
+		encode(string, offset, length, encodeNewline, true, writer);
+	}
+
+	/**
+	 * Variant of {@link #encode} where encodeNbsp and encodeNonLatin are true 
+	 */
+	public static void encode (char[] string, int offset, int length, boolean encodeNewline, boolean encodeSubsequentBlanksToNbsp, Writer writer) throws IOException
+	{
+		encode(string, offset, length, encodeNewline, encodeSubsequentBlanksToNbsp, true, writer);
+	}
+
+
+	/**
+	 * Encodes the given string, so that it can be used within a html page.
+	 * @param string the string to convert
+	 * @param encodeNewline if true newline characters are converted to &lt;br&gt;'s
+	 * @param encodeSubsequentBlanksToNbsp if true subsequent blanks are converted to &amp;nbsp;'s
+	 * @param encodeNonLatin if true encode non-latin characters as numeric character references
+	 */
+	public static void encode (char[] string, int offset, int length,
+								 boolean encodeNewline,
+								 boolean encodeSubsequentBlanksToNbsp,
+								 boolean encodeNonLatin, Writer writer) throws IOException
+	{
+		if (string == null || length < 0 || offset >= string.length)
+		{
+			return;
+		}
+		offset = Math.max(0, offset);
+		int realLength = Math.min(length, string.length - offset);
+
+		StringBuilder sb = null;	//create later on demand
+		String app;
+		char c;
+		
+		for (int i = offset; i < offset + realLength; ++i)
+		{
+			app = null;
+			c = string[i];
+
+			// All characters before letters
+			if ((int)c < 0x41)
+			{
+				switch (c)
+				{
+					case '"': app = "&quot;"; break;    //"
+					case '&': app = "&amp;"; break;     //&
+					case '<': app = "&lt;"; break;      //<
+					case '>': app = "&gt;"; break;      //>
+					case ' ':
+						if (encodeSubsequentBlanksToNbsp &&
+								(i == 0 || (i - 1 >= 0 && string[i - 1] == ' ')))
+						{
+							//Space at beginning or after another space
+							app = "&#160;";
+						}
+						break;
+					case '\n':
+						if (encodeNewline)
+						{
+							app = "<br/>";
+						}
+						break;
+				}
+			} else if (encodeNonLatin && (int)c > 0x80) {
+				 switch(c) {
+					//german umlauts
+					case '\u00E4' : app = "&auml;";  break;
+					case '\u00C4' : app = "&Auml;";  break;
+					case '\u00F6' : app = "&ouml;";  break;
+					case '\u00D6' : app = "&Ouml;";  break;
+					case '\u00FC' : app = "&uuml;";  break;
+					case '\u00DC' : app = "&Uuml;";  break;
+					case '\u00DF' : app = "&szlig;"; break;
+
+					//misc
+					//case 0x80: app = "&euro;"; break;  sometimes euro symbol is ascii 128, should we suport it?
+					case '\u20AC': app = "&euro;";  break;
+					case '\u00AB': app = "&laquo;"; break;
+					case '\u00BB': app = "&raquo;"; break;
+					case '\u00A0': app = "&#160;"; break;
+
+					default :
+						//encode all non basic latin characters
+						app = "&#" + ((int)c) + ";";
+					break;
+				}
+			}
+			if (app != null)
+			{
+				if (sb == null)
+				{
+					sb = new StringBuilder(realLength*2);
+					sb.append(string, offset, i - offset);
+				}
+				sb.append(app);
+			} else {
+				if (sb != null)
+				{
+					sb.append(c);
+				}
+			}
+		}
 
+		if (sb == null)
+		{
+			writer.write(string, offset, realLength);
+		}
+		else
+		{
+			writer.write(sb.toString());
+		}
+	}
 }

Modified: myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/UnicodeEncoder.java
URL: http://svn.apache.org/viewvc/myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/UnicodeEncoder.java?rev=588578&r1=588577&r2=588578&view=diff
==============================================================================
--- myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/UnicodeEncoder.java (original)
+++ myfaces/shared/branches/3_0_1/core/src/main/java/org/apache/myfaces/shared/renderkit/html/util/UnicodeEncoder.java Fri Oct 26 02:19:56 2007
@@ -35,7 +35,7 @@
 			return "";
 		}
 
-		StringBuffer sb = null;
+		StringBuilder sb = null;
 		char c;
 		for (int i = 0; i < string.length (); ++i)
 		{
@@ -43,11 +43,13 @@
 			if (((int)c) >= 0x80)
 			{
 				if( sb == null ){
-					sb = new StringBuffer( string.length()+4 );
+					sb = new StringBuilder( string.length()+4 );
 					sb.append( string.substring(0,i) );
 				}
 				//encode all non basic latin characters
-				sb.append("&#" + ((int)c) + ";");
+				sb.append("&#");
+				sb.append((int)c);
+				sb.append(";");
 			}
 			else if( sb != null )
 			{

Added: myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoderTest.java
URL: http://svn.apache.org/viewvc/myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoderTest.java?rev=588578&view=auto
==============================================================================
--- myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoderTest.java (added)
+++ myfaces/shared/branches/3_0_1/core/src/test/java/org/apache/myfaces/shared/renderkit/html/util/HTMLEncoderTest.java Fri Oct 26 02:19:56 2007
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2007 The Apache Software Foundation.
+ *
+ * Licensed 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.myfaces.shared.renderkit.html.util;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.shale.test.base.AbstractJsfTestCase;
+
+/**
+ * <code>HTMLEncoderTest</code> tests <code>org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder</code>.
+ * 
+ * @author Michael Kurz (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ */
+public class HTMLEncoderTest extends AbstractJsfTestCase {
+  private String stringNoSpecialChars = "Hello, this is MyFaces speaking!";
+  private String stringNoSpecialCharsEncoded = "Hello, this is MyFaces speaking!";
+  private String stringNoSpecialCharsEncodedPartial = "lo, this is MyFaces speakin";
+  private String stringSpecialChars1 = "<\"Hello\", this is MyFaces speaking!>";
+  private String stringSpecialChars1Encoded = "&lt;&quot;Hello&quot;, this is MyFaces speaking!&gt;";
+  private String stringSpecialChars2 = "Hello & this is MyFaces speaking!>";
+  private String stringSpecialChars2Encoded = "Hello &amp; this is MyFaces speaking!&gt;";
+  private String stringLineBreak = "Hell\u00F6\nthis is MyFaces speaking!>";
+  private String stringLineBreakEncoded1 = "Hell&ouml;<br/>this is MyFaces speaking!&gt;";
+  private String stringLineBreakEncoded2 = "Hell&ouml;\nthis is MyFaces speaking!&gt;";
+  private String stringLineBreakEncoded2Partial = "&ouml;\nthis is MyFaces speaking!";
+  private String stringBlanks = "<Hello   this is MyFaces speaking!>";
+  private String stringBlanksEncoded = "&lt;Hello   this is MyFaces speaking!&gt";
+
+  public HTMLEncoderTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Test method for
+   * {@link org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder#encode(String)}.
+   */
+  public void testEncodeStringNoSpecialChars() {
+    String encodedStr = HTMLEncoder.encode(stringNoSpecialChars);
+    assertEquals(stringNoSpecialCharsEncoded, encodedStr);
+  }
+
+  public void testEncodeStringSpecialChars1() {
+    String encodedStr = HTMLEncoder.encode(stringSpecialChars1);
+    assertEquals(stringSpecialChars1Encoded, encodedStr);
+  }
+
+  public void testEncodeStringSpecialChars2() {
+    String encodedStr = HTMLEncoder.encode(stringSpecialChars2);
+    assertEquals(stringSpecialChars2Encoded, encodedStr);
+  }
+
+  public void testEncodeStringLineBreak1() {
+    String encodedStr = HTMLEncoder.encode(stringLineBreak, true);
+    assertEquals(stringLineBreakEncoded1, encodedStr);
+  }
+
+  public void testEncodeStringLineBreak2() {
+    String encodedStr = HTMLEncoder.encode(stringLineBreak, false);
+    assertEquals(stringLineBreakEncoded2, encodedStr);
+  }
+
+  public void testEncodeStringEmpty() {
+    String encodedStr = HTMLEncoder.encode("");
+    assertEquals("", encodedStr);
+  }
+
+  public void testEncodeStringNull() {
+    String encodedStr = HTMLEncoder.encode(null);
+    assertEquals("", encodedStr);
+  }
+
+  public void testEncodeArrayNoSpecialChars() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringNoSpecialChars.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length, writer);
+      assertEquals(stringNoSpecialCharsEncoded.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayNoSpecialCharsPartial() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringNoSpecialChars.toCharArray();
+      HTMLEncoder.encode(source, 3, source.length - 5, writer);
+      assertEquals(stringNoSpecialCharsEncodedPartial.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArraySpecialChars1() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringSpecialChars1.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length, writer);
+      assertEquals(stringSpecialChars1Encoded.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArraySpecialChars2() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringSpecialChars2.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length, writer);
+      assertEquals(stringSpecialChars2Encoded.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayEmpty() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      HTMLEncoder.encode(new char[]{}, 0, 1, writer);
+      assertEquals(new char[]{}, writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayNull() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      HTMLEncoder.encode(null, 0, 0, writer);
+      assertEquals(new char[]{}, writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayWrongIndex1() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringSpecialChars2.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length - 100, writer);
+      assertEquals(new char[]{}, writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayWrongIndex2() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringSpecialChars2.toCharArray();
+      HTMLEncoder.encode(source, -100, source.length, writer);
+      assertEquals(stringSpecialChars2Encoded.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayWrongIndex3() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringSpecialChars2.toCharArray();
+      HTMLEncoder.encode(source, 100000, source.length, writer);
+      assertEquals(new char[]{}, writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayLineBreak1() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringLineBreak.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length, true, writer);
+      assertEquals(stringLineBreakEncoded1.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayLineBreak2() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringLineBreak.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length, false, writer);
+      assertEquals(stringLineBreakEncoded2.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayLineBreak2WrongIndex() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringLineBreak.toCharArray();
+      HTMLEncoder.encode(source, 0, source.length + 5, false, writer);
+      assertEquals(stringLineBreakEncoded2.toCharArray(), writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  public void testEncodeArrayLineBreakPartial() {
+    try {
+      CharArrayWriter writer = new CharArrayWriter();
+      char[] source = stringLineBreak.toCharArray();
+      HTMLEncoder.encode(source, 4, source.length - 5, writer);
+      char[] expected = stringLineBreakEncoded2Partial.toCharArray();
+      assertEquals(expected, writer.toCharArray());
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  private void assertEquals(char[] expected, char[] actual) {
+    if ((expected == null ^ actual == null) || expected.length != actual.length) {
+      fail();
+    }
+    for (int i = 0; i < expected.length; i++) {
+      assertEquals(expected[i], actual[i]);
+    }
+  }
+}
\ No newline at end of file