| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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; |
| import java.io.Serializable; |
| |
| /** |
| * Utilities to manipluate char chunks. While String is |
| * the easiest way to manipulate chars ( search, substrings, etc), |
| * it is known to not be the most efficient solution - Strings are |
| * designed as imutable and secure objects. |
| * |
| * @author dac@sun.com |
| * @author James Todd [gonzo@sun.com] |
| * @author Costin Manolache |
| * @author Remy Maucherat |
| */ |
| public final class CharChunk implements Cloneable, Serializable, CharSequence { |
| |
| // Input interface, used when the buffer is emptied. |
| public static interface CharInputChannel { |
| /** |
| * Read new bytes ( usually the internal conversion buffer ). |
| * The implementation is allowed to ignore the parameters, |
| * and mutate the chunk if it wishes to implement its own buffering. |
| */ |
| public int realReadChars(char cbuf[], int off, int len) |
| throws IOException; |
| } |
| /** |
| * When we need more space we'll either |
| * grow the buffer ( up to the limit ) or send it to a channel. |
| */ |
| public static interface CharOutputChannel { |
| /** Send the bytes ( usually the internal conversion buffer ). |
| * Expect 8k output if the buffer is full. |
| */ |
| public void realWriteChars(char cbuf[], int off, int len) |
| throws IOException; |
| } |
| |
| // -------------------- |
| // char[] |
| private char buff[]; |
| |
| private int start; |
| private int end; |
| |
| private boolean isSet=false; // XXX |
| |
| // -1: grow undefinitely |
| // maximum amount to be cached |
| private int limit=-1; |
| |
| private CharInputChannel in = null; |
| private CharOutputChannel out = null; |
| |
| private boolean optimizedWrite=true; |
| |
| /** |
| * Creates a new, uninitialized CharChunk object. |
| */ |
| public CharChunk() { |
| } |
| |
| public CharChunk(int size) { |
| allocate( size, -1 ); |
| } |
| |
| // -------------------- |
| |
| public CharChunk getClone() { |
| try { |
| return (CharChunk)this.clone(); |
| } catch( Exception ex) { |
| return null; |
| } |
| } |
| |
| public boolean isNull() { |
| if( end > 0 ) return false; |
| return !isSet; //XXX |
| } |
| |
| /** |
| * Resets the message bytes to an uninitialized state. |
| */ |
| public void recycle() { |
| // buff=null; |
| isSet=false; // XXX |
| start=0; |
| end=0; |
| } |
| |
| public void reset() { |
| buff=null; |
| } |
| |
| // -------------------- Setup -------------------- |
| |
| public void allocate( int initial, int limit ) { |
| if( buff==null || buff.length < initial ) { |
| buff=new char[initial]; |
| } |
| this.limit=limit; |
| start=0; |
| end=0; |
| isSet=true; |
| } |
| |
| |
| public void setOptimizedWrite(boolean optimizedWrite) { |
| this.optimizedWrite = optimizedWrite; |
| } |
| |
| public void setChars( char[] c, int off, int len ) { |
| buff=c; |
| start=off; |
| end=start + len; |
| isSet=true; |
| } |
| |
| /** Maximum amount of data in this buffer. |
| * |
| * If -1 or not set, the buffer will grow undefinitely. |
| * Can be smaller than the current buffer size ( which will not shrink ). |
| * When the limit is reached, the buffer will be flushed ( if out is set ) |
| * or throw exception. |
| */ |
| public void setLimit(int limit) { |
| this.limit=limit; |
| } |
| |
| public int getLimit() { |
| return limit; |
| } |
| |
| /** |
| * When the buffer is empty, read the data from the input channel. |
| */ |
| public void setCharInputChannel(CharInputChannel in) { |
| this.in = in; |
| } |
| |
| /** When the buffer is full, write the data to the output channel. |
| * Also used when large amount of data is appended. |
| * |
| * If not set, the buffer will grow to the limit. |
| */ |
| public void setCharOutputChannel(CharOutputChannel out) { |
| this.out=out; |
| } |
| |
| // compat |
| public char[] getChars() |
| { |
| return getBuffer(); |
| } |
| |
| public char[] getBuffer() |
| { |
| return buff; |
| } |
| |
| /** |
| * Returns the start offset of the bytes. |
| * For output this is the end of the buffer. |
| */ |
| public int getStart() { |
| return start; |
| } |
| |
| public int getOffset() { |
| return start; |
| } |
| |
| /** |
| * Returns the start offset of the bytes. |
| */ |
| public void setOffset(int off) { |
| start=off; |
| } |
| |
| /** |
| * Returns the length of the bytes. |
| */ |
| public int getLength() { |
| return end-start; |
| } |
| |
| |
| public int getEnd() { |
| return end; |
| } |
| |
| public void setEnd( int i ) { |
| end=i; |
| } |
| |
| // -------------------- Adding data -------------------- |
| |
| public void append( char b ) |
| throws IOException |
| { |
| makeSpace( 1 ); |
| |
| // couldn't make space |
| if( limit >0 && end >= limit ) { |
| flushBuffer(); |
| } |
| buff[end++]=b; |
| } |
| |
| public void append( CharChunk src ) |
| throws IOException |
| { |
| append( src.getBuffer(), src.getOffset(), src.getLength()); |
| } |
| |
| /** Add data to the buffer |
| */ |
| public void append( char src[], int off, int len ) |
| throws IOException |
| { |
| // will grow, up to limit |
| makeSpace( len ); |
| |
| // if we don't have limit: makeSpace can grow as it wants |
| if( limit < 0 ) { |
| // assert: makeSpace made enough space |
| System.arraycopy( src, off, buff, end, len ); |
| end+=len; |
| return; |
| } |
| |
| // Optimize on a common case. |
| // If the source is going to fill up all the space in buffer, may |
| // as well write it directly to the output, and avoid an extra copy |
| if ( optimizedWrite && len == limit && end == start && out != null ) { |
| out.realWriteChars( src, off, len ); |
| return; |
| } |
| |
| // if we have limit and we're below |
| if( len <= limit - end ) { |
| // makeSpace will grow the buffer to the limit, |
| // so we have space |
| System.arraycopy( src, off, buff, end, len ); |
| |
| end+=len; |
| return; |
| } |
| |
| // need more space than we can afford, need to flush |
| // buffer |
| |
| // the buffer is already at ( or bigger than ) limit |
| |
| // Optimization: |
| // If len-avail < length ( i.e. after we fill the buffer with |
| // what we can, the remaining will fit in the buffer ) we'll just |
| // copy the first part, flush, then copy the second part - 1 write |
| // and still have some space for more. We'll still have 2 writes, but |
| // we write more on the first. |
| |
| if( len + end < 2 * limit ) { |
| /* If the request length exceeds the size of the output buffer, |
| flush the output buffer and then write the data directly. |
| We can't avoid 2 writes, but we can write more on the second |
| */ |
| int avail=limit-end; |
| System.arraycopy(src, off, buff, end, avail); |
| end += avail; |
| |
| flushBuffer(); |
| |
| System.arraycopy(src, off+avail, buff, end, len - avail); |
| end+= len - avail; |
| |
| } else if(optimizedWrite) { // len > buf.length + avail & we have a real sink |
| // long write - flush the buffer and write the rest |
| // directly from source |
| flushBuffer(); |
| |
| out.realWriteChars( src, off, len ); |
| } else { // ugly but it works for fake sinks if they reset us |
| flushBuffer(); |
| append(src, off, len); |
| } |
| } |
| |
| |
| /** Add data to the buffer |
| */ |
| public void append( StringBuffer sb ) |
| throws IOException |
| { |
| int len=sb.length(); |
| |
| // will grow, up to limit |
| makeSpace( len ); |
| |
| // if we don't have limit: makeSpace can grow as it wants |
| if( limit < 0 ) { |
| // assert: makeSpace made enough space |
| sb.getChars(0, len, buff, end ); |
| end+=len; |
| return; |
| } |
| |
| int off=0; |
| int sbOff = off; |
| int sbEnd = off + len; |
| while (sbOff < sbEnd) { |
| int d = min(limit - end, sbEnd - sbOff); |
| sb.getChars( sbOff, sbOff+d, buff, end); |
| sbOff += d; |
| end += d; |
| if (end >= limit) |
| flushBuffer(); |
| } |
| } |
| |
| /** Append a string to the buffer |
| */ |
| public void append(String s) throws IOException { |
| append(s, 0, s.length()); |
| } |
| |
| /** Append a string to the buffer |
| */ |
| public void append(String s, int off, int len) throws IOException { |
| if (s==null) return; |
| |
| // will grow, up to limit |
| makeSpace( len ); |
| |
| // if we don't have limit: makeSpace can grow as it wants |
| if( limit < 0 ) { |
| // assert: makeSpace made enough space |
| s.getChars(off, off+len, buff, end ); |
| end+=len; |
| return; |
| } |
| |
| int sOff = off; |
| int sEnd = off + len; |
| while (sOff < sEnd) { |
| int d = min(limit - end, sEnd - sOff); |
| s.getChars( sOff, sOff+d, buff, end); |
| sOff += d; |
| end += d; |
| if (end >= limit) |
| flushBuffer(); |
| } |
| } |
| |
| // -------------------- Removing data from the buffer -------------------- |
| |
| public int substract() |
| throws IOException { |
| |
| if ((end - start) == 0) { |
| if (in == null) |
| return -1; |
| int n = in.realReadChars(buff, end, buff.length - end); |
| if (n < 0) |
| return -1; |
| } |
| |
| return (buff[start++]); |
| |
| } |
| |
| public int substract(CharChunk src) |
| throws IOException { |
| |
| if ((end - start) == 0) { |
| if (in == null) |
| return -1; |
| int n = in.realReadChars( buff, end, buff.length - end); |
| if (n < 0) |
| return -1; |
| } |
| |
| int len = getLength(); |
| src.append(buff, start, len); |
| start = end; |
| return len; |
| |
| } |
| |
| public int substract( char src[], int off, int len ) |
| throws IOException { |
| |
| if ((end - start) == 0) { |
| if (in == null) |
| return -1; |
| int n = in.realReadChars( buff, end, buff.length - end); |
| if (n < 0) |
| return -1; |
| } |
| |
| int n = len; |
| if (len > getLength()) { |
| n = getLength(); |
| } |
| System.arraycopy(buff, start, src, off, n); |
| start += n; |
| return n; |
| |
| } |
| |
| |
| public void flushBuffer() |
| throws IOException |
| { |
| //assert out!=null |
| if( out==null ) { |
| throw new IOException( "Buffer overflow, no sink " + limit + " " + |
| buff.length ); |
| } |
| out.realWriteChars( buff, start, end - start ); |
| end=start; |
| } |
| |
| /** Make space for len chars. If len is small, allocate |
| * a reserve space too. Never grow bigger than limit. |
| */ |
| private void makeSpace(int count) |
| { |
| char[] tmp = null; |
| |
| int newSize; |
| int desiredSize=end + count; |
| |
| // Can't grow above the limit |
| if( limit > 0 && |
| desiredSize > limit) { |
| desiredSize=limit; |
| } |
| |
| if( buff==null ) { |
| if( desiredSize < 256 ) desiredSize=256; // take a minimum |
| buff=new char[desiredSize]; |
| } |
| |
| // limit < buf.length ( the buffer is already big ) |
| // or we already have space XXX |
| if( desiredSize <= buff.length) { |
| return; |
| } |
| // grow in larger chunks |
| if( desiredSize < 2 * buff.length ) { |
| newSize= buff.length * 2; |
| if( limit >0 && |
| newSize > limit ) newSize=limit; |
| tmp=new char[newSize]; |
| } else { |
| newSize= buff.length * 2 + count ; |
| if( limit > 0 && |
| newSize > limit ) newSize=limit; |
| tmp=new char[newSize]; |
| } |
| |
| System.arraycopy(buff, 0, tmp, 0, end); |
| buff = tmp; |
| tmp = null; |
| } |
| |
| // -------------------- Conversion and getters -------------------- |
| |
| public String toString() { |
| if (null == buff) { |
| return null; |
| } else if (end-start == 0) { |
| return ""; |
| } |
| return StringCache.toString(this); |
| } |
| |
| public String toStringInternal() { |
| return new String(buff, start, end-start); |
| } |
| |
| public int getInt() |
| { |
| return Ascii.parseInt(buff, start, |
| end-start); |
| } |
| |
| // -------------------- 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) { |
| char[] c = buff; |
| int len = end-start; |
| if (c == null || len != s.length()) { |
| return false; |
| } |
| int off = start; |
| for (int i = 0; i < len; i++) { |
| if (c[off++] != s.charAt(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * 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) { |
| char[] c = buff; |
| int len = end-start; |
| if (c == null || len != s.length()) { |
| return false; |
| } |
| int off = start; |
| for (int i = 0; i < len; i++) { |
| if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean equals(CharChunk cc) { |
| return equals( cc.getChars(), cc.getOffset(), cc.getLength()); |
| } |
| |
| public boolean equals(char b2[], int off2, int len2) { |
| char b1[]=buff; |
| if( b1==null && b2==null ) return true; |
| |
| if (b1== null || b2==null || end-start != len2) { |
| return false; |
| } |
| int off1 = start; |
| int len=end-start; |
| while ( len-- > 0) { |
| if (b1[off1++] != b2[off2++]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean equals(byte b2[], int off2, int len2) { |
| char b1[]=buff; |
| if( b2==null && b1==null ) return true; |
| |
| if (b1== null || b2==null || end-start != len2) { |
| return false; |
| } |
| int off1 = start; |
| int len=end-start; |
| |
| while ( len-- > 0) { |
| if ( b1[off1++] != (char)b2[off2++]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns true if the message bytes starts with the specified string. |
| * @param s the string |
| */ |
| public boolean startsWith(String s) { |
| char[] c = buff; |
| int len = s.length(); |
| if (c == null || len > end-start) { |
| return false; |
| } |
| int off = start; |
| for (int i = 0; i < len; i++) { |
| if (c[off++] != s.charAt(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns true if the message bytes starts with the specified string. |
| * @param s the string |
| */ |
| public boolean startsWithIgnoreCase(String s, int pos) { |
| char[] c = buff; |
| int len = s.length(); |
| if (c == null || len+pos > end-start) { |
| return false; |
| } |
| int off = start+pos; |
| for (int i = 0; i < len; i++) { |
| if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| // -------------------- Hash code -------------------- |
| |
| // normal hash. |
| public int hash() { |
| int code=0; |
| for (int i = start; i < start + end-start; i++) { |
| code = code * 37 + buff[i]; |
| } |
| return code; |
| } |
| |
| // hash ignoring case |
| public int hashIgnoreCase() { |
| int code=0; |
| for (int i = start; i < end; i++) { |
| code = code * 37 + Ascii.toLower(buff[i]); |
| } |
| return code; |
| } |
| |
| public int indexOf(char c) { |
| return indexOf( c, start); |
| } |
| |
| /** |
| * Returns true if the message bytes starts with the specified string. |
| * @param c the character |
| */ |
| public int indexOf(char c, int starting) { |
| int ret = indexOf( buff, start+starting, end, c ); |
| return (ret >= start) ? ret - start : -1; |
| } |
| |
| public static int indexOf( char chars[], int off, int cend, char qq ) |
| { |
| while( off < cend ) { |
| char b=chars[off]; |
| if( b==qq ) |
| return off; |
| off++; |
| } |
| return -1; |
| } |
| |
| |
| public int indexOf( String src, int srcOff, int srcLen, int myOff ) { |
| char first=src.charAt( srcOff ); |
| |
| // Look for first char |
| int srcEnd = srcOff + srcLen; |
| |
| for( int i=myOff+start; i <= (end - srcLen); i++ ) { |
| if( buff[i] != first ) continue; |
| // found first char, now look for a match |
| int myPos=i+1; |
| for( int srcPos=srcOff + 1; srcPos< srcEnd; ) { |
| if( buff[myPos++] != src.charAt( srcPos++ )) |
| break; |
| if( srcPos==srcEnd ) return i-start; // found it |
| } |
| } |
| return -1; |
| } |
| |
| // -------------------- utils |
| private int min(int a, int b) { |
| if (a < b) return a; |
| return b; |
| } |
| |
| // Char sequence impl |
| |
| public char charAt(int index) { |
| return buff[index + start]; |
| } |
| |
| public CharSequence subSequence(int start, int end) { |
| try { |
| CharChunk result = (CharChunk) this.clone(); |
| result.setOffset(this.start + start); |
| result.setEnd(this.start + end); |
| return result; |
| } catch (CloneNotSupportedException e) { |
| // Cannot happen |
| return null; |
| } |
| } |
| |
| public int length() { |
| return end - start; |
| } |
| |
| } |