You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2005/10/28 06:11:48 UTC

svn commit: r329081 [2/2] - /tomcat/sandbox/java/org/apache/tomcat/util/buf/

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/MessageBytes.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/MessageBytes.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/MessageBytes.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/MessageBytes.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,731 @@
+/*
+ *  Copyright 1999-2004 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.tomcat.util.buf;
+
+import java.text.*;
+import java.util.*;
+import java.io.Serializable;
+import java.io.IOException;
+
+/**
+ * This class is used to represent a subarray of bytes in an HTTP message.
+ * It represents all request/response elements. The byte/char conversions are
+ * delayed and cached. Everything is recyclable.
+ *
+ * The object can represent a byte[], a char[], or a (sub) String. All
+ * operations can be made in case sensitive mode or not.
+ *
+ * @author dac@eng.sun.com
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Costin Manolache
+ */
+public final class MessageBytes implements Cloneable, Serializable {
+    // primary type ( whatever is set as original value )
+    private int type = T_NULL;
+
+    public static final int T_NULL = 0;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a String */
+    public static final int T_STR  = 1;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a byte[] */ 
+    public static final int T_BYTES = 2;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a char[] */ 
+    public static final int T_CHARS = 3;
+
+    private int hashCode=0;
+    // did we computed the hashcode ? 
+    private boolean hasHashCode=false;
+
+    // Is the represented object case sensitive ?
+    private boolean caseSensitive=true;
+
+    // Internal objects to represent array + offset, and specific methods
+    private ByteChunk byteC=new ByteChunk();
+    private CharChunk charC=new CharChunk();
+    
+    // String
+    private String strValue;
+    // true if a String value was computed. Probably not needed,
+    // strValue!=null is the same
+    private boolean hasStrValue=false;
+
+    /**
+     * Creates a new, uninitialized MessageBytes object.
+     * @deprecated Use static newInstance() in order to allow
+     *   future hooks.
+     */
+    public MessageBytes() {
+    }
+
+    /** Construct a new MessageBytes instance
+     */
+    public static MessageBytes newInstance() {
+	return factory.newInstance();
+    }
+
+    /** Configure the case sensitivity
+     */
+    public void setCaseSenitive( boolean b ) {
+	caseSensitive=b;
+    }
+
+    public MessageBytes getClone() {
+	try {
+	    return (MessageBytes)this.clone();
+	} catch( Exception ex) {
+	    return null;
+	}
+    }
+
+    public boolean isNull() {
+//		should we check also hasStrValue ???
+		return byteC.isNull() && charC.isNull() && ! hasStrValue;
+	// bytes==null && strValue==null;
+    }
+    
+    /**
+     * Resets the message bytes to an uninitialized (NULL) state.
+     */
+    public void recycle() {
+	type=T_NULL;
+	byteC.recycle();
+	charC.recycle();
+
+	strValue=null;
+	caseSensitive=true;
+
+	hasStrValue=false;
+	hasHashCode=false;
+	hasIntValue=false;
+    hasLongValue=false;
+	hasDateValue=false;	
+    }
+
+
+    /**
+     * Sets the content to the specified subarray of bytes.
+     *
+     * @param b the bytes
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     */
+    public void setBytes(byte[] b, int off, int len) {
+        byteC.setBytes( b, off, len );
+        type=T_BYTES;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+        hasDateValue=false; 
+    }
+
+    /** Set the encoding. If the object was constructed from bytes[]. any
+     *  previous conversion is reset.
+     *  If no encoding is set, we'll use 8859-1.
+     */
+    public void setEncoding( String enc ) {
+	if( !byteC.isNull() ) {
+	    // if the encoding changes we need to reset the converion results
+	    charC.recycle();
+	    hasStrValue=false;
+	}
+	byteC.setEncoding(enc);
+    }
+
+    /** 
+     * Sets the content to be a char[]
+     *
+     * @param c the bytes
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     */
+    public void setChars( char[] c, int off, int len ) {
+        charC.setChars( c, off, len );
+        type=T_CHARS;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+        hasDateValue=false; 
+    }
+
+    /** Remove the cached string value. Use it after a conversion on the
+     *	byte[] or after the encoding is changed
+     *  XXX Is this needed ?
+     */
+    public void resetStringValue() {
+	if( type != T_STR ) {
+	    // If this was cread as a byte[] or char[], we remove
+	    // the old string value
+	    hasStrValue=false;
+	    strValue=null;
+	}
+    }
+
+    /** 
+     * Set the content to be a string
+     */
+    public void setString( String s ) {
+        if (s == null)
+            return;
+        strValue=s;
+        hasStrValue=true;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+        hasDateValue=false; 
+        type=T_STR;
+    }
+
+    // -------------------- Conversion and getters --------------------
+
+    /** Compute the string value
+     */
+    public String toString() {
+	if( hasStrValue ) return strValue;
+	hasStrValue=true;
+	
+	switch (type) {
+	case T_CHARS:
+	    strValue=charC.toString();
+	    return strValue;
+	case T_BYTES:
+	    strValue=byteC.toString();
+	    return strValue;
+	}
+	return null;
+    }
+
+    //----------------------------------------
+    /** Return the type of the original content. Can be
+     *  T_STR, T_BYTES, T_CHARS or T_NULL
+     */
+    public int getType() {
+	return type;
+    }
+    
+    /**
+     * Returns the byte chunk, representing the byte[] and offset/length.
+     * Valid only if T_BYTES or after a conversion was made.
+     */
+    public ByteChunk getByteChunk() {
+	return byteC;
+    }
+
+    /**
+     * Returns the char chunk, representing the char[] and offset/length.
+     * Valid only if T_CHARS or after a conversion was made.
+     */
+    public CharChunk getCharChunk() {
+	return charC;
+    }
+
+    /**
+     * Returns the string value.
+     * Valid only if T_STR or after a conversion was made.
+     */
+    public String getString() {
+	return strValue;
+    }
+
+    /** Unimplemented yet. Do a char->byte conversion.
+     */
+    public void toBytes() {
+        if( ! byteC.isNull() ) {
+            type=T_BYTES;
+            return;
+        }
+        toString();
+        type=T_BYTES;
+        byte bb[] = strValue.getBytes();
+        byteC.setBytes(bb, 0, bb.length);
+    }
+
+    /** Convert to char[] and fill the CharChunk.
+     *  XXX Not optimized - it converts to String first.
+     */
+    public void toChars() {
+	if( ! charC.isNull() ) {
+            type=T_CHARS;
+	    return;
+	}
+	// inefficient
+	toString();
+        type=T_CHARS;
+	char cc[]=strValue.toCharArray();
+	charC.setChars(cc, 0, cc.length);
+    }
+    
+
+    /**
+     * Returns the length of the original buffer.
+     * Note that the length in bytes may be different from the length
+     * in chars.
+     */
+    public int getLength() {
+	if(type==T_BYTES)
+	    return byteC.getLength();
+	if(type==T_CHARS) {
+	    return charC.getLength();
+	}
+	if(type==T_STR)
+	    return strValue.length();
+	toString();
+	if( strValue==null ) return 0;
+	return strValue.length();
+    }
+
+    // -------------------- equals --------------------
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+	if( ! caseSensitive )
+	    return equalsIgnoreCase( s );
+	switch (type) {
+	case T_STR:
+	    if( strValue==null && s!=null) return false;
+	    return strValue.equals( s );
+	case T_CHARS:
+	    return charC.equals( s );
+	case T_BYTES:
+	    return byteC.equals( s );
+	default:
+	    return false;
+	}
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+	switch (type) {
+	case T_STR:
+	    if( strValue==null && s!=null) return false;
+	    return strValue.equalsIgnoreCase( s );
+	case T_CHARS:
+	    return charC.equalsIgnoreCase( s );
+	case T_BYTES:
+	    return byteC.equalsIgnoreCase( s );
+	default:
+	    return false;
+	}
+    }
+
+    public boolean equals(MessageBytes mb) {
+	switch (type) {
+	case T_STR:
+	    return mb.equals( strValue );
+	}
+
+	if( mb.type != T_CHARS &&
+	    mb.type!= T_BYTES ) {
+	    // it's a string or int/date string value
+	    return equals( mb.toString() );
+	}
+
+	// mb is either CHARS or BYTES.
+	// this is either CHARS or BYTES
+	// Deal with the 4 cases ( in fact 3, one is simetric)
+	
+	if( mb.type == T_CHARS && type==T_CHARS ) {
+	    return charC.equals( mb.charC );
+	} 
+	if( mb.type==T_BYTES && type== T_BYTES ) {
+	    return byteC.equals( mb.byteC );
+	}
+	if( mb.type== T_CHARS && type== T_BYTES ) {
+	    return byteC.equals( mb.charC );
+	}
+	if( mb.type== T_BYTES && type== T_CHARS ) {
+	    return mb.byteC.equals( charC );
+	}
+	// can't happen
+	return true;
+    }
+
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     */
+    public boolean startsWith(String s) {
+	switch (type) {
+	case T_STR:
+	    return strValue.startsWith( s );
+	case T_CHARS:
+	    return charC.startsWith( s );
+	case T_BYTES:
+	    return byteC.startsWith( s );
+	default:
+	    return false;
+	}
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     * @param pos The start position
+     */
+    public boolean startsWithIgnoreCase(String s, int pos) {
+	switch (type) {
+	case T_STR:
+	    if( strValue==null ) return false;
+	    if( strValue.length() < pos + s.length() ) return false;
+	    
+	    for( int i=0; i<s.length(); i++ ) {
+		if( Ascii.toLower( s.charAt( i ) ) !=
+		    Ascii.toLower( strValue.charAt( pos + i ))) {
+		    return false;
+		}
+	    }
+	    return true;
+	case T_CHARS:
+	    return charC.startsWithIgnoreCase( s, pos );
+	case T_BYTES:
+	    return byteC.startsWithIgnoreCase( s, pos );
+	default:
+	    return false;
+	}
+    }
+
+    
+
+    // -------------------- Hash code  --------------------
+    public  int hashCode() {
+	if( hasHashCode ) return hashCode;
+	int code = 0;
+
+	if( caseSensitive ) 
+	    code=hash(); 
+	else
+	    code=hashIgnoreCase();
+	hashCode=code;
+	hasHashCode=true;
+	return code;
+    }
+
+    // normal hash. 
+    private int hash() {
+	int code=0;
+	switch (type) {
+	case T_STR:
+	    // We need to use the same hash function
+	    for (int i = 0; i < strValue.length(); i++) {
+		code = code * 37 + strValue.charAt( i );
+	    }
+	    return code;
+	case T_CHARS:
+	    return charC.hash();
+	case T_BYTES:
+	    return byteC.hash();
+	default:
+	    return 0;
+	}
+    }
+
+    // hash ignoring case
+    private int hashIgnoreCase() {
+	int code=0;
+	switch (type) {
+	case T_STR:
+	    for (int i = 0; i < strValue.length(); i++) {
+		code = code * 37 + Ascii.toLower(strValue.charAt( i ));
+	    }
+	    return code;
+	case T_CHARS:
+	    return charC.hashIgnoreCase();
+	case T_BYTES:
+	    return byteC.hashIgnoreCase();
+	default:
+	    return 0;
+	}
+    }
+
+    public int indexOf(char c) {
+	return indexOf( c, 0);
+    }
+
+    // Inefficient initial implementation. Will be replaced on the next
+    // round of tune-up
+    public int indexOf(String s, int starting) {
+	toString();
+	return strValue.indexOf( s, starting );
+    }
+    
+    // Inefficient initial implementation. Will be replaced on the next
+    // round of tune-up
+    public int indexOf(String s) {
+	return indexOf( s, 0 );
+    }
+    
+    public int indexOfIgnoreCase(String s, int starting) {
+	toString();
+	String upper=strValue.toUpperCase();
+	String sU=s.toUpperCase();
+	return upper.indexOf( sU, starting );
+    }
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param c the character
+     * @param starting The start position
+     */
+    public int indexOf(char c, int starting) {
+	switch (type) {
+	case T_STR:
+	    return strValue.indexOf( c, starting );
+	case T_CHARS:
+	    return charC.indexOf( c, starting);
+	case T_BYTES:
+	    return byteC.indexOf( c, starting );
+	default:
+	    return -1;
+	}
+    }
+
+    /** Copy the src into this MessageBytes, allocating more space if
+     *  needed
+     */
+    public void duplicate( MessageBytes src ) throws IOException
+    {
+	switch( src.getType() ) {
+	case MessageBytes.T_BYTES:
+	    type=T_BYTES;
+	    ByteChunk bc=src.getByteChunk();
+	    byteC.allocate( 2 * bc.getLength(), -1 );
+	    byteC.append( bc );
+	    break;
+	case MessageBytes.T_CHARS:
+	    type=T_CHARS;
+	    CharChunk cc=src.getCharChunk();
+	    charC.allocate( 2 * cc.getLength(), -1 );
+	    charC.append( cc );
+	    break;
+	case MessageBytes.T_STR:
+	    type=T_STR;
+	    String sc=src.getString();
+	    this.setString( sc );
+	    break;
+	}
+    }
+
+    // -------------------- Deprecated code --------------------
+    // efficient int, long and date
+    // XXX used only for headers - shouldn't be
+    // stored here.
+    private int intValue;
+    private boolean hasIntValue=false;
+    private long longValue;
+    private boolean hasLongValue=false;
+    private Date dateValue;
+    private boolean hasDateValue=false;
+    
+    /**
+     *  @deprecated The buffer are general purpose, caching for headers should
+     *  be done in headers. The second parameter allows us to pass a date format
+     * instance to avoid synchronization problems.
+     */
+    public void setTime(long t, DateFormat df) {
+	// XXX replace it with a byte[] tool
+	recycle();
+	if( dateValue==null)
+	    dateValue=new Date(t);
+	else
+	    dateValue.setTime(t);
+	if( df==null )
+	    strValue=DateTool.format1123(dateValue);
+	else
+	    strValue=DateTool.format1123(dateValue,df);
+	hasStrValue=true;
+	hasDateValue=true;
+	type=T_STR;   
+    }
+
+    public void setTime(long t) {
+	setTime( t, null );
+    }
+
+    /** Set the buffer to the representation of an int
+     */
+    public void setInt(int i) {
+        byteC.allocate(16, 32);
+        int current = i;
+        byte[] buf = byteC.getBuffer();
+        int start = 0;
+        int end = 0;
+        if (i == 0) {
+            buf[end++] = (byte) '0';
+        }
+        if (i < 0) {
+            current = -i;
+            buf[end++] = (byte) '-';
+        }
+        while (current > 0) {
+            int digit = current % 10;
+            current = current / 10;
+            buf[end++] = HexUtils.HEX[digit];
+        }
+        byteC.setOffset(0);
+        byteC.setEnd(end);
+        // Inverting buffer
+        end--;
+        if (i < 0) {
+            start++;
+        }
+        while (end > start) {
+            byte temp = buf[start];
+            buf[start] = buf[end];
+            buf[end] = temp;
+            start++;
+            end--;
+        }
+        intValue=i;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=true;
+        hasLongValue=false;
+        hasDateValue=false; 
+        type=T_BYTES;
+    }
+
+    /** Set the buffer to the representation of an long
+     */
+    public void setLong(long l) {
+        byteC.allocate(32, 64);
+        long current = l;
+        byte[] buf = byteC.getBuffer();
+        int start = 0;
+        int end = 0;
+        if (l == 0) {
+            buf[end++] = (byte) '0';
+        }
+        if (l < 0) {
+            current = -l;
+            buf[end++] = (byte) '-';
+        }
+        while (current > 0) {
+            int digit = (int) (current % 10);
+            current = current / 10;
+            buf[end++] = HexUtils.HEX[digit];
+        }
+        byteC.setOffset(0);
+        byteC.setEnd(end);
+        // Inverting buffer
+        end--;
+        if (l < 0) {
+            start++;
+        }
+        while (end > start) {
+            byte temp = buf[start];
+            buf[start] = buf[end];
+            buf[end] = temp;
+            start++;
+            end--;
+        }
+        longValue=l;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=true;
+        hasDateValue=false; 
+        type=T_BYTES;
+    }
+
+    /**
+     *  @deprecated The buffer are general purpose, caching for headers should
+     *  be done in headers
+     */
+    public  long getTime()
+    {
+     	if( hasDateValue ) {
+	    if( dateValue==null) return -1;
+	    return dateValue.getTime();
+     	}
+	
+     	long l=DateTool.parseDate( this );
+     	if( dateValue==null)
+     	    dateValue=new Date(l);
+     	else
+     	    dateValue.setTime(l);
+     	hasDateValue=true;
+     	return l;
+    }
+    
+
+    // Used for headers conversion
+    /** Convert the buffer to an int, cache the value
+     */ 
+    public int getInt() 
+    {
+	if( hasIntValue )
+	    return intValue;
+	
+	switch (type) {
+	case T_BYTES:
+	    intValue=byteC.getInt();
+	    break;
+	default:
+	    intValue=Integer.parseInt(toString());
+	}
+	hasIntValue=true;
+	return intValue;
+    }
+
+    // Used for headers conversion
+    /** Convert the buffer to an long, cache the value
+     */ 
+    public long getLong() {
+        if( hasLongValue )
+            return longValue;
+        
+        switch (type) {
+        case T_BYTES:
+            longValue=byteC.getLong();
+            break;
+        default:
+            longValue=Long.parseLong(toString());
+        }
+
+        hasLongValue=true;
+        return longValue;
+
+     }
+
+    // -------------------- Future may be different --------------------
+    
+    private static MessageBytesFactory factory=new MessageBytesFactory();
+
+    public static void setFactory( MessageBytesFactory mbf ) {
+	factory=mbf;
+    }
+    
+    public static class MessageBytesFactory {
+	protected MessageBytesFactory() {
+	}
+	public MessageBytes newInstance() {
+	    return new MessageBytes();
+	}
+    }
+}

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/StringCache.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/StringCache.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/StringCache.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/StringCache.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,669 @@
+/*
+ *  Copyright 1999-2005 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.tomcat.util.buf;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.TreeMap;
+
+/**
+ * This class implements a String cache for ByteChunk and CharChunk.
+ *
+ * @author Remy Maucherat
+ */
+public class StringCache {
+
+
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog( StringCache.class );
+    
+    
+    // ------------------------------------------------------- Static Variables
+
+    
+    /**
+     * Enabled ?
+     */
+    protected static boolean byteEnabled = 
+        ("true".equals(System.getProperty("tomcat.util.buf.StringCache.byte.enabled", "false")));
+
+    
+    protected static boolean charEnabled = 
+        ("true".equals(System.getProperty("tomcat.util.buf.StringCache.char.enabled", "false")));
+
+    
+    protected static int trainThreshold = 
+        Integer.parseInt(System.getProperty("tomcat.util.buf.StringCache.trainThreshold", "20000"));
+    
+
+    protected static int cacheSize = 
+        Integer.parseInt(System.getProperty("tomcat.util.buf.StringCache.cacheSize", "200"));
+    
+
+    /**
+     * Statistics hash map for byte chunk.
+     */
+    protected static HashMap bcStats = new HashMap(cacheSize);
+
+    
+    /**
+     * toString count for byte chunk.
+     */
+    protected static int bcCount = 0;
+    
+    
+    /**
+     * Cache for byte chunk.
+     */
+    protected static ByteEntry[] bcCache = null;
+    
+
+    /**
+     * Statistics hash map for char chunk.
+     */
+    protected static HashMap ccStats = new HashMap(cacheSize);
+
+
+    /**
+     * toString count for char chunk.
+     */
+    protected static int ccCount = 0; 
+    
+
+    /**
+     * Cache for char chunk.
+     */
+    protected static CharEntry[] ccCache = null;
+
+    
+    /**
+     * Access count.
+     */
+    protected static int accessCount = 0;
+    
+
+    /**
+     * Hit count.
+     */
+    protected static int hitCount = 0;
+    
+
+    // ------------------------------------------------------------ Properties
+
+    
+    /**
+     * @return Returns the cacheSize.
+     */
+    public int getCacheSize() {
+        return cacheSize;
+    }
+    
+    
+    /**
+     * @param cacheSize The cacheSize to set.
+     */
+    public void setCacheSize(int cacheSize) {
+        StringCache.cacheSize = cacheSize;
+    }
+
+    
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getByteEnabled() {
+        return byteEnabled;
+    }
+    
+    
+    /**
+     * @param byteEnabled The enabled to set.
+     */
+    public void setByteEnabled(boolean byteEnabled) {
+        StringCache.byteEnabled = byteEnabled;
+    }
+    
+    
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getCharEnabled() {
+        return charEnabled;
+    }
+    
+    
+    /**
+     * @param charEnabled The enabled to set.
+     */
+    public void setCharEnabled(boolean charEnabled) {
+        StringCache.charEnabled = charEnabled;
+    }
+    
+    
+    /**
+     * @return Returns the trainThreshold.
+     */
+    public int getTrainThreshold() {
+        return trainThreshold;
+    }
+    
+    
+    /**
+     * @param trainThreshold The trainThreshold to set.
+     */
+    public void setTrainThreshold(int trainThreshold) {
+        StringCache.trainThreshold = trainThreshold;
+    }
+
+    
+    /**
+     * @return Returns the accessCount.
+     */
+    public int getAccessCount() {
+        return accessCount;
+    }
+    
+    
+    /**
+     * @return Returns the hitCount.
+     */
+    public int getHitCount() {
+        return hitCount;
+    }
+
+    
+    // -------------------------------------------------- Public Static Methods
+
+    
+    public void reset() {
+        hitCount = 0;
+        accessCount = 0;
+        synchronized (bcStats) {
+            bcCache = null;
+            bcCount = 0;
+        }
+        synchronized (ccStats) {
+            ccCache = null;
+            ccCount = 0;
+        }
+    }
+    
+    
+    public static String toString(ByteChunk bc) {
+
+        // If the cache is null, then either caching is disabled, or we're
+        // still training
+        if (bcCache == null) {
+            String value = bc.toStringInternal();
+            if (byteEnabled) {
+                // If training, everything is synced
+                synchronized (bcStats) {
+                    // If the cache has been generated on a previous invocation
+                    // while waiting fot the lock, just return the toString value
+                    // we just calculated
+                    if (bcCache != null) {
+                        return value;
+                    }
+                    // Two cases: either we just exceeded the train count, in which
+                    // case the cache must be created, or we just update the count for
+                    // the string
+                    if (bcCount > trainThreshold) {
+                        long t1 = System.currentTimeMillis();
+                        // Sort the entries according to occurrence
+                        TreeMap tempMap = new TreeMap();
+                        Iterator entries = bcStats.keySet().iterator();
+                        while (entries.hasNext()) {
+                            ByteEntry entry = (ByteEntry) entries.next();
+                            int[] countA = (int[]) bcStats.get(entry);
+                            Integer count = new Integer(countA[0]);
+                            // Add to the list for that count
+                            ArrayList list = (ArrayList) tempMap.get(count);
+                            if (list == null) {
+                                // Create list
+                                list = new ArrayList();
+                                tempMap.put(count, list);
+                            }
+                            list.add(entry);
+                        }
+                        // Allocate array of the right size
+                        int size = bcStats.size();
+                        if (size > cacheSize) {
+                            size = cacheSize;
+                        }
+                        ByteEntry[] tempbcCache = new ByteEntry[size];
+                        // Fill it up using an alphabetical order
+                        // and a dumb insert sort
+                        ByteChunk tempChunk = new ByteChunk();
+                        int n = 0;
+                        while (n < size) {
+                            Object key = tempMap.lastKey();
+                            ArrayList list = (ArrayList) tempMap.get(key);
+                            ByteEntry[] list2 = 
+                                (ByteEntry[]) list.toArray(new ByteEntry[list.size()]);
+                            for (int i = 0; i < list.size() && n < size; i++) {
+                                ByteEntry entry = (ByteEntry) list.get(i);
+                                tempChunk.setBytes(entry.name, 0, entry.name.length);
+                                int insertPos = findClosest(tempChunk, tempbcCache, n);
+                                if (insertPos == n) {
+                                    tempbcCache[n + 1] = entry;
+                                } else {
+                                    System.arraycopy(tempbcCache, insertPos + 1, tempbcCache, 
+                                            insertPos + 2, n - insertPos - 1);
+                                    tempbcCache[insertPos + 1] = entry;
+                                }
+                                n++;
+                            }
+                            tempMap.remove(key);
+                        }
+                        bcCount = 0;
+                        bcStats.clear();
+                        bcCache = tempbcCache;
+                        if (log.isDebugEnabled()) {
+                            long t2 = System.currentTimeMillis();
+                            log.debug("ByteCache generation time: " + (t2 - t1) + "ms");
+                        }
+                    } else {
+                        bcCount++;
+                        // Allocate new ByteEntry for the lookup
+                        ByteEntry entry = new ByteEntry();
+                        entry.value = value;
+                        int[] count = (int[]) bcStats.get(entry);
+                        if (count == null) {
+                            int end = bc.getEnd();
+                            int start = bc.getStart();
+                            // Create byte array and copy bytes
+                            entry.name = new byte[bc.getLength()];
+                            System.arraycopy(bc.getBuffer(), start, entry.name, 0, end - start);
+                            // Set encoding
+                            entry.enc = bc.getEncoding();
+                            // Initialize occurrence count to one 
+                            count = new int[1];
+                            count[0] = 1;
+                            // Set in the stats hash map
+                            bcStats.put(entry, count);
+                        } else {
+                            count[0] = count[0] + 1;
+                        }
+                    }
+                }
+            }
+            return value;
+        } else {
+            accessCount++;
+            // Find the corresponding String
+            String result = find(bc);
+            if (result == null) {
+                return bc.toStringInternal();
+            }
+            // Note: We don't care about safety for the stats
+            hitCount++;
+            return result;
+        }
+        
+    }
+
+
+    public static String toString(CharChunk cc) {
+        
+        // If the cache is null, then either caching is disabled, or we're
+        // still training
+        if (ccCache == null) {
+            String value = cc.toStringInternal();
+            if (charEnabled) {
+                // If training, everything is synced
+                synchronized (ccStats) {
+                    // If the cache has been generated on a previous invocation
+                    // while waiting fot the lock, just return the toString value
+                    // we just calculated
+                    if (ccCache != null) {
+                        return value;
+                    }
+                    // Two cases: either we just exceeded the train count, in which
+                    // case the cache must be created, or we just update the count for
+                    // the string
+                    if (ccCount > trainThreshold) {
+                        long t1 = System.currentTimeMillis();
+                        // Sort the entries according to occurrence
+                        TreeMap tempMap = new TreeMap();
+                        Iterator entries = ccStats.keySet().iterator();
+                        while (entries.hasNext()) {
+                            CharEntry entry = (CharEntry) entries.next();
+                            int[] countA = (int[]) ccStats.get(entry);
+                            Integer count = new Integer(countA[0]);
+                            // Add to the list for that count
+                            ArrayList list = (ArrayList) tempMap.get(count);
+                            if (list == null) {
+                                // Create list
+                                list = new ArrayList();
+                                tempMap.put(count, list);
+                            }
+                            list.add(entry);
+                        }
+                        // Allocate array of the right size
+                        int size = ccStats.size();
+                        if (size > cacheSize) {
+                            size = cacheSize;
+                        }
+                        CharEntry[] tempccCache = new CharEntry[size];
+                        // Fill it up using an alphabetical order
+                        // and a dumb insert sort
+                        CharChunk tempChunk = new CharChunk();
+                        int n = 0;
+                        while (n < size) {
+                            Object key = tempMap.lastKey();
+                            ArrayList list = (ArrayList) tempMap.get(key);
+                            CharEntry[] list2 = 
+                                (CharEntry[]) list.toArray(new CharEntry[list.size()]);
+                            for (int i = 0; i < list.size() && n < size; i++) {
+                                CharEntry entry = (CharEntry) list.get(i);
+                                tempChunk.setChars(entry.name, 0, entry.name.length);
+                                int insertPos = findClosest(tempChunk, tempccCache, n);
+                                if (insertPos == n) {
+                                    tempccCache[n + 1] = entry;
+                                } else {
+                                    System.arraycopy(tempccCache, insertPos + 1, tempccCache, 
+                                            insertPos + 2, n - insertPos - 1);
+                                    tempccCache[insertPos + 1] = entry;
+                                }
+                                n++;
+                            }
+                            tempMap.remove(key);
+                        }
+                        ccCount = 0;
+                        ccStats.clear();
+                        ccCache = tempccCache;
+                        if (log.isDebugEnabled()) {
+                            long t2 = System.currentTimeMillis();
+                            log.debug("CharCache generation time: " + (t2 - t1) + "ms");
+                        }
+                    } else {
+                        ccCount++;
+                        // Allocate new CharEntry for the lookup
+                        CharEntry entry = new CharEntry();
+                        entry.value = value;
+                        int[] count = (int[]) ccStats.get(entry);
+                        if (count == null) {
+                            int end = cc.getEnd();
+                            int start = cc.getStart();
+                            // Create char array and copy chars
+                            entry.name = new char[cc.getLength()];
+                            System.arraycopy(cc.getBuffer(), start, entry.name, 0, end - start);
+                            // Initialize occurrence count to one 
+                            count = new int[1];
+                            count[0] = 1;
+                            // Set in the stats hash map
+                            ccStats.put(entry, count);
+                        } else {
+                            count[0] = count[0] + 1;
+                        }
+                    }
+                }
+            }
+            return value;
+        } else {
+            accessCount++;
+            // Find the corresponding String
+            String result = find(cc);
+            if (result == null) {
+                return cc.toStringInternal();
+            }
+            // Note: We don't care about safety for the stats
+            hitCount++;
+            return result;
+        }
+        
+    }
+    
+    
+    // ----------------------------------------------------- Protected Methods
+
+
+    /**
+     * Compare given byte chunk with byte array.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    protected static final int compare(ByteChunk name, byte[] compareTo) {
+        int result = 0;
+
+        byte[] b = name.getBuffer();
+        int start = name.getStart();
+        int end = name.getEnd();
+        int len = compareTo.length;
+
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (b[i + start] > compareTo[i]) {
+                result = 1;
+            } else if (b[i + start] < compareTo[i]) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length > (end - start)) {
+                result = -1;
+            } else if (compareTo.length < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * Find an entry given its name in the cache and return the associated String.
+     */
+    protected static final String find(ByteChunk name) {
+        int pos = findClosest(name, bcCache, bcCache.length);
+        if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
+                || !(name.getEncoding().equals(bcCache[pos].enc))) {
+            return null;
+        } else {
+            return bcCache[pos].value;
+        }
+    }
+
+    
+    /**
+     * Find an entry given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    protected static final int findClosest(ByteChunk name, ByteEntry[] array, int len) {
+
+        int a = 0;
+        int b = len - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, array[0].name) < 0) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compare(name, array[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, array[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Compare given char chunk with char array.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    protected static final int compare(CharChunk name, char[] compareTo) {
+        int result = 0;
+
+        char[] c = name.getBuffer();
+        int start = name.getStart();
+        int end = name.getEnd();
+        int len = compareTo.length;
+
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (c[i + start] > compareTo[i]) {
+                result = 1;
+            } else if (c[i + start] < compareTo[i]) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length > (end - start)) {
+                result = -1;
+            } else if (compareTo.length < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * Find an entry given its name in the cache and return the associated String.
+     */
+    protected static final String find(CharChunk name) {
+        int pos = findClosest(name, ccCache, ccCache.length);
+        if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
+            return null;
+        } else {
+            return ccCache[pos].value;
+        }
+    }
+
+    
+    /**
+     * Find an entry given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    protected static final int findClosest(CharChunk name, CharEntry[] array, int len) {
+
+        int a = 0;
+        int b = len - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, array[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compare(name, array[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, array[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    // -------------------------------------------------- ByteEntry Inner Class
+
+
+    public static class ByteEntry {
+
+        public byte[] name = null;
+        public String enc = null;
+        public String value = null;
+
+        public String toString() {
+            return value;
+        }
+        public int hashCode() {
+            return value.hashCode();
+        }
+        public boolean equals(Object obj) {
+            if (obj instanceof ByteEntry) {
+                return value.equals(((ByteEntry) obj).value);
+            }
+            return false;
+        }
+        
+    }
+
+
+    // -------------------------------------------------- CharEntry Inner Class
+
+
+    public static class CharEntry {
+
+        public char[] name = null;
+        public String value = null;
+
+        public String toString() {
+            return value;
+        }
+        public int hashCode() {
+            return value.hashCode();
+        }
+        public boolean equals(Object obj) {
+            if (obj instanceof CharEntry) {
+                return value.equals(((CharEntry) obj).value);
+            }
+            return false;
+        }
+        
+    }
+
+
+}

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/TimeStamp.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/TimeStamp.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/TimeStamp.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/TimeStamp.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,155 @@
+/*
+ *  Copyright 1999-2004 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.tomcat.util.buf;
+
+import java.io.Serializable;
+
+// XXX Shouldn't be here - has nothing to do with buffers.
+
+/**
+ * Main tool for object expiry. 
+ * Marks creation and access time of an "expirable" object,
+ * and extra properties like "id", "valid", etc.
+ *
+ * Used for objects that expire - originally Sessions, but 
+ * also Contexts, Servlets, cache - or any other object that
+ * expires.
+ * 
+ * @author Costin Manolache
+ */
+public final class TimeStamp implements  Serializable {
+    private long creationTime = 0L;
+    private long lastAccessedTime = creationTime;
+    private long thisAccessedTime = creationTime;
+    private boolean isNew = true;
+    private long maxInactiveInterval = -1;
+    private boolean isValid = false;
+    MessageBytes name;
+    int id=-1;
+    
+    Object parent;
+    
+    public TimeStamp() {
+    }
+
+    // -------------------- Active methods --------------------
+
+    /**
+     *  Access notification. This method takes a time parameter in order
+     *  to allow callers to efficiently manage expensive calls to
+     *  System.currentTimeMillis() 
+     */
+    public void touch(long time) {
+	this.lastAccessedTime = this.thisAccessedTime;
+	this.thisAccessedTime = time;
+	this.isNew=false;
+    }
+
+    // -------------------- Property access --------------------
+
+    /** Return the "name" of the timestamp. This can be used
+     *  to associate unique identifier with each timestamped object.
+     *  The name is a MessageBytes - i.e. a modifiable byte[] or char[]. 
+     */
+    public MessageBytes getName() {
+	if( name==null ) name=MessageBytes.newInstance();//lazy
+	return name;
+    }
+
+    /** Each object can have an unique id, similar with name but
+     *  providing faster access ( array vs. hashtable lookup )
+     */
+    public int getId() {
+	return id;
+    }
+
+    public void setId( int id ) {
+	this.id=id;
+    }
+    
+    /** Returns the owner of this stamp ( the object that is
+     *  time-stamped ).
+     *  For a 
+     */
+    public void setParent( Object o ) {
+	parent=o;
+    }
+
+    public Object getParent() {
+	return parent;
+    }
+
+    public void setCreationTime(long time) {
+	this.creationTime = time;
+	this.lastAccessedTime = time;
+	this.thisAccessedTime = time;
+    }
+
+
+    public long getLastAccessedTime() {
+	return lastAccessedTime;
+    }
+
+    public long getThisAccessedTime() {
+        return thisAccessedTime;
+    }
+
+    /** Inactive interval in millis - the time is computed
+     *  in millis, convert to secs in the upper layer
+     */
+    public long getMaxInactiveInterval() {
+	return maxInactiveInterval;
+    }
+
+    public void setMaxInactiveInterval(long interval) {
+	maxInactiveInterval = interval;
+    }
+
+    public boolean isValid() {
+	return isValid;
+    }
+
+    public void setValid(boolean isValid) {
+	this.isValid = isValid;
+    }
+
+    public boolean isNew() {
+	return isNew;
+    }
+
+    public void setNew(boolean isNew) {
+	this.isNew = isNew;
+    }
+
+    public long getCreationTime() {
+	return creationTime;
+    }
+
+    // -------------------- Maintainance --------------------
+
+    public void recycle() {
+	creationTime = 0L;
+	lastAccessedTime = 0L;
+	maxInactiveInterval = -1;
+	isNew = true;
+	isValid = false;
+	id=-1;
+	if( name!=null) name.recycle();
+    }
+
+}
+

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/UDecoder.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/UDecoder.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/UDecoder.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/UDecoder.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,276 @@
+/*
+ *  Copyright 1999-2004 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.tomcat.util.buf;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+
+/** 
+ *  All URL decoding happens here. This way we can reuse, review, optimize
+ *  without adding complexity to the buffers.
+ *
+ *  The conversion will modify the original buffer.
+ * 
+ *  @author Costin Manolache
+ */
+public final class UDecoder {
+    
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog(UDecoder.class );
+    
+    public UDecoder() 
+    {
+    }
+
+    /** URLDecode, will modify the source.  Includes converting
+     *  '+' to ' '.
+     */
+    public void convert( ByteChunk mb )
+        throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** URLDecode, will modify the source.
+     */
+    public void convert( ByteChunk mb, boolean query )
+	throws IOException
+    {
+	int start=mb.getOffset();
+	byte buff[]=mb.getBytes();
+	int end=mb.getEnd();
+
+	int idx= ByteChunk.indexOf( buff, start, end, '%' );
+        int idx2=-1;
+        if( query )
+            idx2= ByteChunk.indexOf( buff, start, end, '+' );
+	if( idx<0 && idx2<0 ) {
+	    return;
+	}
+
+	// idx will be the smallest positive inxes ( first % or + )
+	if( idx2 >= 0 && idx2 < idx ) idx=idx2;
+	if( idx < 0 ) idx=idx2;
+
+	for( int j=idx; j<end; j++, idx++ ) {
+	    if( buff[ j ] == '+' && query) {
+		buff[idx]= (byte)' ' ;
+	    } else if( buff[ j ] != '%' ) {
+		buff[idx]= buff[j];
+	    } else {
+		// read next 2 digits
+		if( j+2 >= end ) {
+		    throw new CharConversionException("EOF");
+		}
+		byte b1= buff[j+1];
+		byte b2=buff[j+2];
+		if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+		    throw new CharConversionException( "isHexDigit");
+		
+		j+=2;
+		int res=x2c( b1, b2 );
+		buff[idx]=(byte)res;
+	    }
+	}
+
+	mb.setEnd( idx );
+	
+	return;
+    }
+
+    // -------------------- Additional methods --------------------
+    // XXX What do we do about charset ????
+
+    /** In-buffer processing - the buffer will be modified
+     *  Includes converting  '+' to ' '.
+     */
+    public void convert( CharChunk mb )
+	throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** In-buffer processing - the buffer will be modified
+     */
+    public void convert( CharChunk mb, boolean query )
+	throws IOException
+    {
+	//	log( "Converting a char chunk ");
+	int start=mb.getOffset();
+	char buff[]=mb.getBuffer();
+	int cend=mb.getEnd();
+
+	int idx= CharChunk.indexOf( buff, start, cend, '%' );
+        int idx2=-1;
+        if( query )
+            idx2= CharChunk.indexOf( buff, start, cend, '+' );
+	if( idx<0 && idx2<0 ) {
+	    return;
+	}
+	
+	if( idx2 >= 0 && idx2 < idx ) idx=idx2; 
+	if( idx < 0 ) idx=idx2;
+
+	for( int j=idx; j<cend; j++, idx++ ) {
+	    if( buff[ j ] == '+' && query ) {
+		buff[idx]=( ' ' );
+	    } else if( buff[ j ] != '%' ) {
+		buff[idx]=buff[j];
+	    } else {
+		// read next 2 digits
+		if( j+2 >= cend ) {
+		    // invalid
+		    throw new CharConversionException("EOF");
+		}
+		char b1= buff[j+1];
+		char b2=buff[j+2];
+		if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+		    throw new CharConversionException("isHexDigit");
+		
+		j+=2;
+		int res=x2c( b1, b2 );
+		buff[idx]=(char)res;
+	    }
+	}
+	mb.setEnd( idx );
+    }
+
+    /** URLDecode, will modify the source
+     *  Includes converting  '+' to ' '.
+     */
+    public void convert(MessageBytes mb)
+	throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** URLDecode, will modify the source
+     */
+    public void convert(MessageBytes mb, boolean query)
+	throws IOException
+    {
+	
+	switch (mb.getType()) {
+	case MessageBytes.T_STR:
+	    String strValue=mb.toString();
+	    if( strValue==null ) return;
+	    mb.setString( convert( strValue, query ));
+	    break;
+	case MessageBytes.T_CHARS:
+	    CharChunk charC=mb.getCharChunk();
+	    convert( charC, query );
+	    break;
+	case MessageBytes.T_BYTES:
+	    ByteChunk bytesC=mb.getByteChunk();
+	    convert( bytesC, query );
+	    break;
+	}
+    }
+
+    // XXX Old code, needs to be replaced !!!!
+    // 
+    public final String convert(String str)
+    {
+        return convert(str, true);
+    }
+
+    public final String convert(String str, boolean query)
+    {
+        if (str == null)  return  null;
+	
+	if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 )
+	    return str;
+	
+        StringBuffer dec = new StringBuffer();    // decoded string output
+        int strPos = 0;
+        int strLen = str.length();
+
+        dec.ensureCapacity(str.length());
+        while (strPos < strLen) {
+            int laPos;        // lookahead position
+
+            // look ahead to next URLencoded metacharacter, if any
+            for (laPos = strPos; laPos < strLen; laPos++) {
+                char laChar = str.charAt(laPos);
+                if ((laChar == '+' && query) || (laChar == '%')) {
+                    break;
+                }
+            }
+
+            // if there were non-metacharacters, copy them all as a block
+            if (laPos > strPos) {
+                dec.append(str.substring(strPos,laPos));
+                strPos = laPos;
+            }
+
+            // shortcut out of here if we're at the end of the string
+            if (strPos >= strLen) {
+                break;
+            }
+
+            // process next metacharacter
+            char metaChar = str.charAt(strPos);
+            if (metaChar == '+') {
+                dec.append(' ');
+                strPos++;
+                continue;
+            } else if (metaChar == '%') {
+		// We throw the original exception - the super will deal with
+		// it
+		//                try {
+		dec.append((char)Integer.
+			   parseInt(str.substring(strPos + 1, strPos + 3),16));
+                strPos += 3;
+            }
+        }
+
+        return dec.toString();
+    }
+
+
+
+    private static boolean isHexDigit( int c ) {
+	return ( ( c>='0' && c<='9' ) ||
+		 ( c>='a' && c<='f' ) ||
+		 ( c>='A' && c<='F' ));
+    }
+    
+    private static int x2c( byte b1, byte b2 ) {
+	int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
+	    (b1 -'0');
+	digit*=16;
+	digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
+	    (b2 -'0');
+	return digit;
+    }
+
+    private static int x2c( char b1, char b2 ) {
+	int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
+	    (b1 -'0');
+	digit*=16;
+	digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
+	    (b2 -'0');
+	return digit;
+    }
+
+    private final static int debug=0;
+    private static void log( String s ) {
+        if (log.isDebugEnabled())
+            log.debug("URLDecoder: " + s );
+    }
+
+}

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/UEncoder.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/UEncoder.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/UEncoder.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/UEncoder.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,181 @@
+/*
+ *  Copyright 1999-2004 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.tomcat.util.buf;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.BitSet;
+
+/** Efficient implementation for encoders.
+ *  This class is not thread safe - you need one encoder per thread.
+ *  The encoder will save and recycle the internal objects, avoiding
+ *  garbage.
+ * 
+ *  You can add extra characters that you want preserved, for example
+ *  while encoding a URL you can add "/".
+ *
+ *  @author Costin Manolache
+ */
+public final class UEncoder {
+
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog(UEncoder.class );
+    
+    // Not static - the set may differ ( it's better than adding
+    // an extra check for "/", "+", etc
+    private BitSet safeChars=null;
+    private C2BConverter c2b=null;
+    private ByteChunk bb=null;
+
+    private String encoding="UTF8";
+    private static final int debug=0;
+    
+    public UEncoder() {
+	initSafeChars();
+    }
+
+    public void setEncoding( String s ) {
+	encoding=s;
+    }
+
+    public void addSafeCharacter( char c ) {
+	safeChars.set( c );
+    }
+
+
+    /** URL Encode string, using a specified encoding.
+     *
+     * @param buf The writer
+     * @param s string to be encoded
+     * @throws IOException If an I/O error occurs
+     */
+    public void urlEncode( Writer buf, String s )
+	throws IOException
+    {
+	if( c2b==null ) {
+	    bb=new ByteChunk(16); // small enough.
+	    c2b=new C2BConverter( bb, encoding );
+	}
+
+	for (int i = 0; i < s.length(); i++) {
+	    int c = (int) s.charAt(i);
+	    if( safeChars.get( c ) ) {
+		if( debug > 0 ) log("Safe: " + (char)c);
+		buf.write((char)c);
+	    } else {
+		if( debug > 0 ) log("Unsafe:  " + (char)c);
+		c2b.convert( (char)c );
+		
+		// "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
+		// ( while UCS is 31 ). Amazing...
+		if (c >= 0xD800 && c <= 0xDBFF) {
+		    if ( (i+1) < s.length()) {
+			int d = (int) s.charAt(i+1);
+			if (d >= 0xDC00 && d <= 0xDFFF) {
+			    if( debug > 0 ) log("Unsafe:  " + c);
+			    c2b.convert( (char)d);
+			    i++;
+			}
+		    }
+		}
+
+		c2b.flushBuffer();
+		
+		urlEncode( buf, bb.getBuffer(), bb.getOffset(),
+			   bb.getLength() );
+		bb.recycle();
+	    }
+	}
+    }
+
+    /**
+     */
+    public void urlEncode( Writer buf, byte bytes[], int off, int len)
+	throws IOException
+    {
+	for( int j=off; j< len; j++ ) {
+	    buf.write( '%' );
+	    char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
+	    if( debug > 0 ) log("Encode:  " + ch);
+	    buf.write(ch);
+	    ch = Character.forDigit(bytes[j] & 0xF, 16);
+	    if( debug > 0 ) log("Encode:  " + ch);
+	    buf.write(ch);
+	}
+    }
+    
+    /**
+     * Utility funtion to re-encode the URL.
+     * Still has problems with charset, since UEncoder mostly
+     * ignores it.
+     */
+    public String encodeURL(String uri) {
+	String outUri=null;
+	try {
+	    // XXX optimize - recycle, etc
+	    CharArrayWriter out = new CharArrayWriter();
+	    urlEncode(out, uri);
+	    outUri=out.toString();
+	} catch (IOException iex) {
+	}
+	return outUri;
+    }
+    
+
+    // -------------------- Internal implementation --------------------
+    
+    // 
+    private void init() {
+	
+    }
+    
+    private void initSafeChars() {
+	safeChars=new BitSet(128);
+	int i;
+	for (i = 'a'; i <= 'z'; i++) {
+	    safeChars.set(i);
+	}
+	for (i = 'A'; i <= 'Z'; i++) {
+	    safeChars.set(i);
+	}
+	for (i = '0'; i <= '9'; i++) {
+	    safeChars.set(i);
+	}
+	//safe
+	safeChars.set('$');
+	safeChars.set('-');
+	safeChars.set('_');
+	safeChars.set('.');
+
+	// Dangerous: someone may treat this as " "
+	// RFC1738 does allow it, it's not reserved
+	//    safeChars.set('+');
+	//extra
+	safeChars.set('!');
+	safeChars.set('*');
+	safeChars.set('\'');
+	safeChars.set('(');
+	safeChars.set(')');
+	safeChars.set(',');	
+    }
+
+    private static void log( String s ) {
+        if (log.isDebugEnabled())
+            log.debug("Encoder: " + s );
+    }
+}

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/UTF8Decoder.java
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/UTF8Decoder.java?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/UTF8Decoder.java (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/UTF8Decoder.java Thu Oct 27 21:11:42 2005
@@ -0,0 +1,149 @@
+/*
+ *  Copyright 1999-2004 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.tomcat.util.buf;
+
+import java.io.IOException;
+
+/**
+ * Moved from ByteChunk - code to convert from UTF8 bytes to chars.
+ * Not used in the current tomcat3.3 : the performance gain is not very
+ * big if the String is created, only if we avoid that and work only
+ * on char[]. Until than, it's better to be safe. ( I tested this code
+ * with 2 and 3 bytes chars, and it works fine in xerces )
+ * 
+ * Cut from xerces' UTF8Reader.copyMultiByteCharData() 
+ *
+ * @author Costin Manolache
+ * @author ( Xml-Xerces )
+ */
+public final class UTF8Decoder extends B2CConverter {
+    
+    
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog(UTF8Decoder.class );
+    
+    // may have state !!
+    
+    public UTF8Decoder() {
+
+    }
+    
+    public void recycle() {
+    }
+
+    public void convert(ByteChunk mb, CharChunk cb )
+	throws IOException
+    {
+	int bytesOff=mb.getOffset();
+	int bytesLen=mb.getLength();
+	byte bytes[]=mb.getBytes();
+	
+	int j=bytesOff;
+	int end=j+bytesLen;
+
+	while( j< end ) {
+	    int b0=0xff & bytes[j];
+
+	    if( (b0 & 0x80) == 0 ) {
+		cb.append((char)b0);
+		j++;
+		continue;
+	    }
+	    
+	    // 2 byte ?
+	    if( j++ >= end ) {
+		// ok, just ignore - we could throw exception
+		throw new IOException( "Conversion error - EOF " );
+	    }
+	    int b1=0xff & bytes[j];
+	    
+	    // ok, let's the fun begin - we're handling UTF8
+	    if ((0xe0 & b0) == 0xc0) { // 110yyyyy 10xxxxxx (0x80 to 0x7ff)
+		int ch = ((0x1f & b0)<<6) + (0x3f & b1);
+		if(debug>0)
+		    log("Convert " + b0 + " " + b1 + " " + ch + ((char)ch));
+		
+		cb.append((char)ch);
+		j++;
+		continue;
+	    }
+	    
+	    if( j++ >= end ) 
+		return ;
+	    int b2=0xff & bytes[j];
+	    
+	    if( (b0 & 0xf0 ) == 0xe0 ) {
+		if ((b0 == 0xED && b1 >= 0xA0) ||
+		    (b0 == 0xEF && b1 == 0xBF && b2 >= 0xBE)) {
+		    if(debug>0)
+			log("Error " + b0 + " " + b1+ " " + b2 );
+
+		    throw new IOException( "Conversion error 2"); 
+		}
+
+		int ch = ((0x0f & b0)<<12) + ((0x3f & b1)<<6) + (0x3f & b2);
+		cb.append((char)ch);
+		if(debug>0)
+		    log("Convert " + b0 + " " + b1+ " " + b2 + " " + ch +
+			((char)ch));
+		j++;
+		continue;
+	    }
+
+	    if( j++ >= end ) 
+		return ;
+	    int b3=0xff & bytes[j];
+
+	    if (( 0xf8 & b0 ) == 0xf0 ) {
+		if (b0 > 0xF4 || (b0 == 0xF4 && b1 >= 0x90)) {
+		    if(debug>0)
+			log("Convert " + b0 + " " + b1+ " " + b2 + " " + b3);
+		    throw new IOException( "Conversion error ");
+		}
+		int ch = ((0x0f & b0)<<18) + ((0x3f & b1)<<12) +
+		    ((0x3f & b2)<<6) + (0x3f & b3);
+
+		if(debug>0)
+		    log("Convert " + b0 + " " + b1+ " " + b2 + " " + b3 + " " +
+			ch + ((char)ch));
+
+		if (ch < 0x10000) {
+		    cb.append( (char)ch );
+		} else {
+		    cb.append((char)(((ch-0x00010000)>>10)+
+						   0xd800));
+		    cb.append((char)(((ch-0x00010000)&0x3ff)+
+						   0xdc00));
+		}
+		j++;
+		continue;
+	    } else {
+		// XXX Throw conversion exception !!!
+		if(debug>0)
+		    log("Convert " + b0 + " " + b1+ " " + b2 + " " + b3);
+		throw new IOException( "Conversion error 4" );
+	    }
+	}
+    }
+
+    private static int debug=1;
+    void log(String s ) {
+        if (log.isDebugEnabled())
+            log.debug("UTF8Decoder: " + s );
+    }
+    
+}

Added: tomcat/sandbox/java/org/apache/tomcat/util/buf/package.html
URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/tomcat/util/buf/package.html?rev=329081&view=auto
==============================================================================
--- tomcat/sandbox/java/org/apache/tomcat/util/buf/package.html (added)
+++ tomcat/sandbox/java/org/apache/tomcat/util/buf/package.html Thu Oct 27 21:11:42 2005
@@ -0,0 +1,22 @@
+<html><body>
+<H1>Buffers and Encodings</h1>
+
+This package contains buffers and utils to perform encoding/decoding of buffers. That includes byte to char
+conversions, URL encodings, etc. 
+
+<p>
+Encoding is a critical operation for performance. There are few tricks in this package - the C2B and 
+B2C converters are caching a ISReader/OSWriter and keep everything allocated to do the conversions 
+in any VM without any garbage.
+
+<p>
+This package must accomodate future extensions and additional converters ( most imporant: the nio.charset, 
+which should be detected and used if available ). Also, we do have one hand-written UTF8Decoder, and 
+other tuned encoders could be added.
+
+<p>
+My benchmarks ( I'm costin :-) show only small differences between C2B, B2C and hand-written codders/decoders,
+so UTF8Decoder may be disabled. 
+
+<p>
+</body></html>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org