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