Index: java/org/apache/turbine/DynamicURI.java
===================================================================
RCS file: /home/cvs/jakarta-turbine-3/src/java/org/apache/turbine/DynamicURI.java,v
retrieving revision 1.5
diff -u -r1.5 DynamicURI.java
--- java/org/apache/turbine/DynamicURI.java	5 Mar 2002 01:32:34 -0000	1.5
+++ java/org/apache/turbine/DynamicURI.java	20 Mar 2002 00:15:19 -0000
@@ -54,14 +54,17 @@
  * <http://www.apache.org/>.
  */
 
-import org.apache.turbine.RunData;
-import java.net.URLEncoder;
 import java.util.Enumeration;
-import java.util.Vector;
+import java.util.ArrayList;
+import java.util.List;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import org.apache.turbine.RunData;
 import org.apache.turbine.Turbine;
 import org.apache.turbine.ParameterParser;
+import org.apache.commons.httpclient.URIUtil;
 
 /**
  * This creates a Dynamic URI for use within the Turbine system
@@ -73,8 +76,8 @@
  *
  * <code><pre>
  * DynamicURI dui = new DynamicURI (data, "UserScreen" );
- * dui.setName("Click Here").addPathInfo("user","jon");
- * dui.getA();
+ * dui.addPathInfo("user","jon");
+ * dui.getA("Click Here");
  * </pre></code>
  *
  * The above call to getA() would return the String:
@@ -100,23 +103,11 @@
     /** Servlet response interface. */
     public HttpServletResponse res = null;
 
-    /** A Vector that contains all the path info if any. */
-    protected Vector pathInfo = null;
-
-    /** A Vectory that contains all the query data if any. */
-    protected Vector queryData = null;
-
-    /**
-     * Fast shortcut to determine if there is any data in the path
-     * info.
-     */
-    protected boolean hasPathInfo = false;
+    /** A list that contains all the path info if any. */
+    protected List pathInfo = null;
 
-    /**
-     * Fast shortcut to determine if there is any data in the query
-     * data.
-     */
-    protected boolean hasQueryData = false;
+    /** A list that contains all the query data if any. */
+    protected List queryData = null;
 
     /** Whether we want to redirect or not. */
     protected boolean redirect = false;
@@ -230,7 +221,7 @@
     /**
      * Initialize with a RunData object
      *
-     * @param RunData
+     * @param data A Turbine RunData object.
      */
     public void init( RunData data )
     {
@@ -255,17 +246,15 @@
                        String value )
     {
         Object[] tmp = new Object[2];
-        tmp[0] = (Object) data.getParameters().convertAndTrim(name);
-        tmp[1] = (Object) value;
+        tmp[0] = data.getParameters().convertAndTrim(name);
+        tmp[1] = value;
         switch (type)
         {
         case PATH_INFO:
-            this.pathInfo.addElement ( tmp );
-            this.hasPathInfo = true;
+            this.pathInfo.add ( tmp );
             break;
         case QUERY_DATA:
-            this.queryData.addElement ( tmp );
-            this.hasQueryData = true;
+            this.queryData.add ( tmp );
             break;
         }
     }
@@ -290,12 +279,10 @@
         while ( e.hasMoreElements() )
         {
             String key = (String)e.nextElement();
-            if ( !key.equalsIgnoreCase(Turbine.ACTION) &&
-                 !key.equalsIgnoreCase(Turbine.SCREEN) &&
-                 !key.equalsIgnoreCase(Turbine.TEMPLATE) )
+            if ( !isReservedKey(key) )
             {
                 String[] values = pp.getStrings(key);
-                for ( int i=0; i<values.length; i++ )
+                for ( int i = 0; i < values.length; i++ )
                 {
                     add( type, key, values[i] );
                 }
@@ -304,6 +291,25 @@
     }
 
     /**
+     * Check whether the given key is a reserved one for a key/value
+     * pair. As a default it checks for 'screen', 'action' and 'template'.
+     * Override this method if you use more reserved keys in your webapp
+     * and you would like to avoid a <tt>ParameterParser</tt> addition to break.
+     *
+     * @param key the key to check for validity. It is typically a key of a key/value
+     * pair (query string or path info)
+     * @return <tt>true</tt> if the given string is a reserved key
+     * in this current implementation.
+     * @see #add(int, ParameterParser)
+     */
+    protected boolean isReservedKey(String key)
+    {
+        return ( Turbine.ACTION.equalsIgnoreCase(key) ||
+                 Turbine.SCREEN.equalsIgnoreCase(key) ||
+                 Turbine.TEMPLATE.equalsIgnoreCase(key) );
+    }
+
+    /**
      * Adds a name=value pair to the path_info string.
      *
      * @param name A String with the name to add.
@@ -452,8 +458,8 @@
      *
      * <code><pre>
      * DynamicURI dui = new DynamicURI (data, "UserScreen" );
-     * dui.setName("Click Here").addPathInfo("user","jon");
-     * dui.getA();
+     * dui.addPathInfo("user","jon");
+     * dui.getA("Click Here");
      * </pre></code>
      *
      * would return the String:
@@ -465,13 +471,18 @@
      */
     public String getA( String name )
     {
-        return new StringBuffer("<a href=\"")
-            .append(this.toString())
+        final String s = this.toString();
+        // I'm being a pit picky about size here to avoid useless
+        // StringBuffer reallocation
+        final int size = s.length() + name.length()
+            + "<a href=\"\"></a>".length();
+        return new StringBuffer( size )
+            .append("<a href=\"")
+            .append(s)
             .append("\">")
             .append(name)
             .append("</a>")
             .toString();
-        //return new A(this.toString(), name).toString();
     }
 
     /**
@@ -519,8 +530,8 @@
      */
     protected void init()
     {
-        this.pathInfo = new Vector();
-        this.queryData = new Vector();
+        this.pathInfo = new ArrayList();
+        this.queryData = new ArrayList();
     }
 
     /**
@@ -541,36 +552,10 @@
             switch (type)
             {
             case PATH_INFO:
-                for (Enumeration e = this.pathInfo.elements() ;
-                     e.hasMoreElements() ;)
-                {
-                    Object[] tmp = (Object[]) e.nextElement();
-                    if ( data.getParameters().convertAndTrim(name)
-                         .equals ( (String)tmp[0] ) )
-                    {
-                        this.pathInfo.removeElement ( tmp );
-                    }
-                }
-                if ( hasPathInfo && this.pathInfo.size() == 0 )
-                {
-                    this.hasPathInfo = false;
-                }
+                removePairByName(this.pathInfo, name);
                 break;
             case QUERY_DATA:
-                for (Enumeration e = this.queryData.elements() ;
-                     e.hasMoreElements() ;)
-                {
-                    Object[] tmp = (Object[]) e.nextElement();
-                    if ( data.getParameters().convertAndTrim(name)
-                         .equals ( (String)tmp[0] ) )
-                    {
-                        this.queryData.removeElement ( tmp );
-                    }
-                }
-                if ( hasQueryData && this.queryData.size() == 0 )
-                {
-                    this.hasQueryData = false;
-                }
+                removePairByName(this.queryData, name);
                 break;
             }
         }
@@ -580,12 +565,32 @@
     }
 
     /**
+     * Helper method to remove one or more pairs by its name (ie key).
+     * It is intended to be used with <tt>queryData</tt> and <tt>pathInfo</tt>.
+     * @param pairs the list of pairs to look over for removal.
+     * @param name the name of the pair(s) to remove.
+     */
+    protected void removePairByName(List pairs, String name)
+    {
+        name = data.getParameters().convertAndTrim(name);
+        // CAUTION: the dynamic evaluation of the size is on purpose because
+        // elements may be removed on the fly.
+        for (int i = 0; i < pairs.size(); i++)
+        {
+            Object[] pair = (Object[])pairs.get(i);
+            if ( name.equals( (String)pair[0] ) )
+            {
+                pairs.remove(i);
+            }
+        }
+    }
+
+    /**
      * Removes all the path info elements.
      */
     public void removePathInfo ()
     {
-        this.pathInfo.removeAllElements();
-        this.hasPathInfo = false;
+        this.pathInfo.clear();
     }
 
     /**
@@ -603,8 +608,7 @@
      */
     public void removeQueryData ()
     {
-        this.queryData.removeAllElements();
-        this.hasQueryData = false;
+        this.queryData.clear();
     }
 
     /**
@@ -621,78 +625,71 @@
      * This method takes a Vector of key/value arrays and converts it
      * into a URL encoded querystring format.
      *
-     * @param data A Vector of key/value arrays.
-     * @return A String with the URL encoded data.
+     * @param out the buffer to render the query string to.
+     * @param pairs A List of key/value arrays.
+     * @return the reference to <tt>out</tt> for chaining convenience.
      */
-    protected String renderPathInfo ( Vector data )
+    protected StringBuffer renderPathInfo(StringBuffer out, List pairs)
     {
-        String key = null;
-        String value = null;
-        String tmp = null;
-        StringBuffer out = new StringBuffer();
-        Enumeration keys = data.elements();
-        while(keys.hasMoreElements())
-        {
-            Object[] stuff = (Object[]) keys.nextElement();
-            key = URLEncoder.encode((String)stuff[0]);
-            tmp = (String) stuff[1];
-            if (tmp == null || tmp.length() == 0)
-            {
-                value = "null";
-            }
-            else
-            {
-                value = URLEncoder.encode(tmp);
-            }
-
-            if (out.length() > 0)
-            {
-                out.append ( "/" );
-            }
-            out.append ( key );
-            out.append ( "/" );
-            out.append ( value );
-        }
-        return out.toString();
+        return renderPairs(out, pairs, '/', '/');
     }
 
     /**
      * This method takes a Vector of key/value arrays and converts it
      * into a URL encoded querystring format.
      *
-     * @param data A Vector of key/value arrays.
-     * @return A String with the URL encoded data.
+     * @param out the buffer to render the query string to.
+     * @param pairs A List of key/value arrays.
+     * @return the reference to <tt>out</tt> for chaining convenience.
      */
-    protected String renderQueryString ( Vector data )
+    protected StringBuffer renderQueryString ( StringBuffer out, List pairs )
+    {
+        return renderPairs(out, pairs, '&', '=');
+    }
+
+    /**
+     * This method takes a Vector of key/value arrays and converts it
+     * into a URL encoded key/value pair format with the appropriate
+     * separator.
+     *
+     * @param out the buffer to write the pairs to.
+     * @param pairs A List of key/value arrays.
+     * @param pairSeparator the character to use as a separator between pairs.
+     * For example for a query-like rendering it would be '&'.
+     * @param keyvalueSeparator the character to use as a separator between
+     * key and value. For example for a query-like rendering, it would be '='.
+     * @return the reference to the <tt>out</tt> for chaining convenience.
+     */
+    protected StringBuffer renderPairs(StringBuffer out,
+      List pairs, char pairSeparator, char keyvalueSeparator)
     {
         String key = null;
         String value = null;
         String tmp = null;
-        StringBuffer out = new StringBuffer();
-        Enumeration keys = data.elements();
-        while(keys.hasMoreElements())
-        {
-            Object[] stuff = (Object[]) keys.nextElement();
-            key   = URLEncoder.encode((String) stuff[0]);
-            tmp = (String) stuff[1];
+        final int count = pairs.size();
+        for (int i = 0; i < count; i++)
+        {
+            Object[] pair = (Object[]) pairs.get(i);
+            key = URIUtil.encode((String)pair[0], true);
+            tmp = (String) pair[1];
             if (tmp == null || tmp.length() == 0)
             {
                 value = "null";
             }
             else
             {
-                value = URLEncoder.encode(tmp);
+                value = URIUtil.encode(tmp, true);
             }
 
-            if ( out.length() > 0)
+            if (i > 0)
             {
-                out.append ( "&" );
+                out.append ( pairSeparator );
             }
             out.append ( key );
-            out.append ( "=" );
+            out.append ( keyvalueSeparator );
             out.append ( value );
         }
-        return out.toString();
+        return out;
     }
 
     /**
@@ -716,7 +713,7 @@
      * <p>By default it adds the information to the path_info instead
      * of the query data.
      *
-     * @param action A String with the screen value.
+     * @param screen A String with the screen value.
      * @return A DynamicURI (self).
      */
     public DynamicURI setScreen ( String screen )
@@ -804,6 +801,7 @@
         return encodeUrl;
     }
 
+
     /**
      * Builds the URL with all of the data URL-encoded as well as
      * encoded using HttpServletResponse.encodeUrl().
@@ -824,39 +822,41 @@
      */
     public String toString()
     {
-        StringBuffer output = new StringBuffer();
+        StringBuffer output = new StringBuffer(128);
         if (!isRelative)
         {
-            output.append ( getServerScheme() );
+            final String scheme = getServerScheme();
+            output.append ( scheme );
             output.append ( "://" );
             output.append ( getServerName() );
-            if ( (getServerScheme().equals(HTTP) && getServerPort() != 80)
-                 || (getServerScheme().equals(HTTPS) && getServerPort() != 443)
+            final int port = getServerPort();
+            if ( (HTTP.equals(scheme) && port != 80)
+                 || (HTTPS.equals(scheme) && port != 443)
                )
             {
                 output.append (':');
-                output.append ( getServerPort() );
+                output.append ( port );
             }
         }
 
         output.append ( getScriptName() );
-        if ( this.hasPathInfo )
+        if ( !pathInfo.isEmpty() )
         {
             output.append ('/');
-            output.append ( renderPathInfo(this.pathInfo) );
+            renderPathInfo(output, pathInfo);
         }
-        if ( this.hasQueryData )
+        if ( !queryData.isEmpty() )
         {
             output.append ('?');
-            output.append ( renderQueryString(this.queryData) );
+            renderQueryString(output, queryData);
         }
 
         // There seems to be a bug in Apache JServ 1.0 where the
         // session id is not appended to the end of the url when a
         // cookie has not been set.
-        if ( this.res != null && encodeUrl )
+        if ( res != null && encodeUrl )
         {
-            if ( this.redirect )
+            if ( redirect )
             {
                 return res.encodeRedirectURL (output.toString());
             }
@@ -894,24 +894,23 @@
      * would not.
      *
      * @param data A Turbine RunData object.
-     * @param boolean to determine absolute vs. relative links.
+     * @param isAbsolute to determine absolute vs. relative links.
      * @return A String with the URL representing the RunData.
      */
     public static String toString(RunData data, boolean isAbsolute)
     {
-        StringBuffer output = new StringBuffer();
+        StringBuffer output = new StringBuffer(128);
         HttpServletRequest request = data.getRequest();
 
         if (isAbsolute)
         {
-            output.append (data.getServerScheme());
+            final String scheme = data.getServerScheme();
+            output.append (scheme);
             output.append ( "://" );
             output.append (data.getServerName());
-            
-            if ( (data.getServerScheme().equals(HTTP) &&
-                  data.getServerPort() != 80) ||
-                 (data.getServerScheme().equals(HTTPS) &&
-                  data.getServerPort() != 443) )
+            final int port = data.getServerPort();
+            if ( (HTTP.equals(scheme) && port != 80) ||
+                 (HTTPS.equals(scheme) && port != 443) )
             {
                 output.append (':');
                 output.append (data.getServerPort());
Index: java/org/apache/turbine/RelativeDynamicURI.java
===================================================================
RCS file: /home/cvs/jakarta-turbine-3/src/java/org/apache/turbine/RelativeDynamicURI.java,v
retrieving revision 1.4
diff -u -r1.4 RelativeDynamicURI.java
--- java/org/apache/turbine/RelativeDynamicURI.java	5 Mar 2002 01:32:34 -0000	1.4
+++ java/org/apache/turbine/RelativeDynamicURI.java	20 Mar 2002 00:15:23 -0000
@@ -54,9 +54,7 @@
  * <http://www.apache.org/>.
  */
 
-import java.util.Vector;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import org.apache.turbine.RunData;
 
 /**
@@ -71,8 +69,8 @@
  *
  * <code><pre>
  * RelativeDynamicURI dui = new RelativeDynamicURI (data, "UserScreen" );
- * dui.setName("Click Here").addPathInfo("user","jon");
- * dui.getA();
+ * dui.addPathInfo("user","jon");
+ * dui.getA("Click Here");
  * </pre></code>
  *
  * The above call to getA() would return the String:
@@ -135,17 +133,17 @@
      */
     public String toString()
     {
-        StringBuffer output = new StringBuffer();
+        StringBuffer output = new StringBuffer(128);
         output.append ( getScriptName() );
-        if ( this.hasPathInfo )
+        if ( !pathInfo.isEmpty() )
         {
             output.append ( "/" );
-            output.append ( renderPathInfo(this.pathInfo) );
+            renderPathInfo(output, pathInfo);
         }
-        if ( this.hasQueryData )
+        if ( !queryData.isEmpty() )
         {
             output.append ( "?" );
-            output.append ( renderQueryString(this.queryData) );
+            renderQueryString(output, queryData);
         }
 
         // There seems to be a bug in Apache JServ 1.0 where the
@@ -176,7 +174,7 @@
      */
     public static String toString(RunData data)
     {
-        StringBuffer output = new StringBuffer();
+        StringBuffer output = new StringBuffer(128);
         HttpServletRequest request = data.getRequest();
 
         output.append ( data.getScriptName() );
