001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.tomcat.util.buf;
018:
019: import java.io.IOException;
020: import java.io.Serializable;
021:
022: /**
023: * Utilities to manipluate char chunks. While String is
024: * the easiest way to manipulate chars ( search, substrings, etc),
025: * it is known to not be the most efficient solution - Strings are
026: * designed as imutable and secure objects.
027: *
028: * @author dac@sun.com
029: * @author James Todd [gonzo@sun.com]
030: * @author Costin Manolache
031: * @author Remy Maucherat
032: */
033: public final class CharChunk implements Cloneable, Serializable {
034:
035: // Input interface, used when the buffer is emptied.
036: public static interface CharInputChannel {
037: /**
038: * Read new bytes ( usually the internal conversion buffer ).
039: * The implementation is allowed to ignore the parameters,
040: * and mutate the chunk if it wishes to implement its own buffering.
041: */
042: public int realReadChars(char cbuf[], int off, int len)
043: throws IOException;
044: }
045:
046: /**
047: * When we need more space we'll either
048: * grow the buffer ( up to the limit ) or send it to a channel.
049: */
050: public static interface CharOutputChannel {
051: /** Send the bytes ( usually the internal conversion buffer ).
052: * Expect 8k output if the buffer is full.
053: */
054: public void realWriteChars(char cbuf[], int off, int len)
055: throws IOException;
056: }
057:
058: // --------------------
059: // char[]
060: private char buff[];
061:
062: private int start;
063: private int end;
064:
065: private boolean isSet = false; // XXX
066:
067: private boolean isOutput = false;
068:
069: // -1: grow undefinitely
070: // maximum amount to be cached
071: private int limit = -1;
072:
073: private CharInputChannel in = null;
074: private CharOutputChannel out = null;
075:
076: private boolean optimizedWrite = true;
077:
078: /**
079: * Creates a new, uninitialized CharChunk object.
080: */
081: public CharChunk() {
082: }
083:
084: public CharChunk(int size) {
085: allocate(size, -1);
086: }
087:
088: // --------------------
089:
090: public CharChunk getClone() {
091: try {
092: return (CharChunk) this .clone();
093: } catch (Exception ex) {
094: return null;
095: }
096: }
097:
098: public boolean isNull() {
099: if (end > 0)
100: return false;
101: return !isSet; //XXX
102: }
103:
104: /**
105: * Resets the message bytes to an uninitialized state.
106: */
107: public void recycle() {
108: // buff=null;
109: isSet = false; // XXX
110: start = 0;
111: end = 0;
112: }
113:
114: public void reset() {
115: buff = null;
116: }
117:
118: // -------------------- Setup --------------------
119:
120: public void allocate(int initial, int limit) {
121: isOutput = true;
122: if (buff == null || buff.length < initial) {
123: buff = new char[initial];
124: }
125: this .limit = limit;
126: start = 0;
127: end = 0;
128: isOutput = true;
129: isSet = true;
130: }
131:
132: public void setOptimizedWrite(boolean optimizedWrite) {
133: this .optimizedWrite = optimizedWrite;
134: }
135:
136: public void setChars(char[] c, int off, int len) {
137: recycle();
138: isSet = true;
139: buff = c;
140: start = off;
141: end = start + len;
142: limit = end;
143: }
144:
145: /** Maximum amount of data in this buffer.
146: *
147: * If -1 or not set, the buffer will grow undefinitely.
148: * Can be smaller than the current buffer size ( which will not shrink ).
149: * When the limit is reached, the buffer will be flushed ( if out is set )
150: * or throw exception.
151: */
152: public void setLimit(int limit) {
153: this .limit = limit;
154: }
155:
156: public int getLimit() {
157: return limit;
158: }
159:
160: /**
161: * When the buffer is empty, read the data from the input channel.
162: */
163: public void setCharInputChannel(CharInputChannel in) {
164: this .in = in;
165: }
166:
167: /** When the buffer is full, write the data to the output channel.
168: * Also used when large amount of data is appended.
169: *
170: * If not set, the buffer will grow to the limit.
171: */
172: public void setCharOutputChannel(CharOutputChannel out) {
173: this .out = out;
174: }
175:
176: // compat
177: public char[] getChars() {
178: return getBuffer();
179: }
180:
181: public char[] getBuffer() {
182: return buff;
183: }
184:
185: /**
186: * Returns the start offset of the bytes.
187: * For output this is the end of the buffer.
188: */
189: public int getStart() {
190: return start;
191: }
192:
193: public int getOffset() {
194: return getStart();
195: }
196:
197: /**
198: * Returns the start offset of the bytes.
199: */
200: public void setOffset(int off) {
201: start = off;
202: }
203:
204: /**
205: * Returns the length of the bytes.
206: */
207: public int getLength() {
208: return end - start;
209: }
210:
211: public int getEnd() {
212: return end;
213: }
214:
215: public void setEnd(int i) {
216: end = i;
217: }
218:
219: // -------------------- Adding data --------------------
220:
221: public void append(char b) throws IOException {
222: makeSpace(1);
223:
224: // couldn't make space
225: if (limit > 0 && end >= limit) {
226: flushBuffer();
227: }
228: buff[end++] = b;
229: }
230:
231: public void append(CharChunk src) throws IOException {
232: append(src.getBuffer(), src.getOffset(), src.getLength());
233: }
234:
235: /** Add data to the buffer
236: */
237: public void append(char src[], int off, int len) throws IOException {
238: // will grow, up to limit
239: makeSpace(len);
240:
241: // if we don't have limit: makeSpace can grow as it wants
242: if (limit < 0) {
243: // assert: makeSpace made enough space
244: System.arraycopy(src, off, buff, end, len);
245: end += len;
246: return;
247: }
248:
249: // Optimize on a common case.
250: // If the source is going to fill up all the space in buffer, may
251: // as well write it directly to the output, and avoid an extra copy
252: if (optimizedWrite && len == limit && end == start) {
253: out.realWriteChars(src, off, len);
254: return;
255: }
256:
257: // if we have limit and we're below
258: if (len <= limit - end) {
259: // makeSpace will grow the buffer to the limit,
260: // so we have space
261: System.arraycopy(src, off, buff, end, len);
262:
263: end += len;
264: return;
265: }
266:
267: // need more space than we can afford, need to flush
268: // buffer
269:
270: // the buffer is already at ( or bigger than ) limit
271:
272: // Optimization:
273: // If len-avail < length ( i.e. after we fill the buffer with
274: // what we can, the remaining will fit in the buffer ) we'll just
275: // copy the first part, flush, then copy the second part - 1 write
276: // and still have some space for more. We'll still have 2 writes, but
277: // we write more on the first.
278:
279: if (len + end < 2 * limit) {
280: /* If the request length exceeds the size of the output buffer,
281: flush the output buffer and then write the data directly.
282: We can't avoid 2 writes, but we can write more on the second
283: */
284: int avail = limit - end;
285: System.arraycopy(src, off, buff, end, avail);
286: end += avail;
287:
288: flushBuffer();
289:
290: System.arraycopy(src, off + avail, buff, end, len - avail);
291: end += len - avail;
292:
293: } else { // len > buf.length + avail
294: // long write - flush the buffer and write the rest
295: // directly from source
296: flushBuffer();
297:
298: out.realWriteChars(src, off, len);
299: }
300: }
301:
302: /** Add data to the buffer
303: */
304: public void append(StringBuffer sb) throws IOException {
305: int len = sb.length();
306:
307: // will grow, up to limit
308: makeSpace(len);
309:
310: // if we don't have limit: makeSpace can grow as it wants
311: if (limit < 0) {
312: // assert: makeSpace made enough space
313: sb.getChars(0, len, buff, end);
314: end += len;
315: return;
316: }
317:
318: int off = 0;
319: int sbOff = off;
320: int sbEnd = off + len;
321: while (sbOff < sbEnd) {
322: int d = min(limit - end, sbEnd - sbOff);
323: sb.getChars(sbOff, sbOff + d, buff, end);
324: sbOff += d;
325: end += d;
326: if (end >= limit)
327: flushBuffer();
328: }
329: }
330:
331: /** Append a string to the buffer
332: */
333: public void append(String s, int off, int len) throws IOException {
334: if (s == null)
335: return;
336:
337: // will grow, up to limit
338: makeSpace(len);
339:
340: // if we don't have limit: makeSpace can grow as it wants
341: if (limit < 0) {
342: // assert: makeSpace made enough space
343: s.getChars(off, off + len, buff, end);
344: end += len;
345: return;
346: }
347:
348: int sOff = off;
349: int sEnd = off + len;
350: while (sOff < sEnd) {
351: int d = min(limit - end, sEnd - sOff);
352: s.getChars(sOff, sOff + d, buff, end);
353: sOff += d;
354: end += d;
355: if (end >= limit)
356: flushBuffer();
357: }
358: }
359:
360: // -------------------- Removing data from the buffer --------------------
361:
362: public int substract() throws IOException {
363:
364: if ((end - start) == 0) {
365: if (in == null)
366: return -1;
367: int n = in.realReadChars(buff, end, buff.length - end);
368: if (n < 0)
369: return -1;
370: }
371:
372: return (buff[start++]);
373:
374: }
375:
376: public int substract(CharChunk src) throws IOException {
377:
378: if ((end - start) == 0) {
379: if (in == null)
380: return -1;
381: int n = in.realReadChars(buff, end, buff.length - end);
382: if (n < 0)
383: return -1;
384: }
385:
386: int len = getLength();
387: src.append(buff, start, len);
388: start = end;
389: return len;
390:
391: }
392:
393: public int substract(char src[], int off, int len)
394: throws IOException {
395:
396: if ((end - start) == 0) {
397: if (in == null)
398: return -1;
399: int n = in.realReadChars(buff, end, buff.length - end);
400: if (n < 0)
401: return -1;
402: }
403:
404: int n = len;
405: if (len > getLength()) {
406: n = getLength();
407: }
408: System.arraycopy(buff, start, src, off, n);
409: start += n;
410: return n;
411:
412: }
413:
414: public void flushBuffer() throws IOException {
415: //assert out!=null
416: if (out == null) {
417: throw new IOException("Buffer overflow, no sink " + limit
418: + " " + buff.length);
419: }
420: out.realWriteChars(buff, start, end - start);
421: end = start;
422: }
423:
424: /** Make space for len chars. If len is small, allocate
425: * a reserve space too. Never grow bigger than limit.
426: */
427: private void makeSpace(int count) {
428: char[] tmp = null;
429:
430: int newSize;
431: int desiredSize = end + count;
432:
433: // Can't grow above the limit
434: if (limit > 0 && desiredSize > limit) {
435: desiredSize = limit;
436: }
437:
438: if (buff == null) {
439: if (desiredSize < 256)
440: desiredSize = 256; // take a minimum
441: buff = new char[desiredSize];
442: }
443:
444: // limit < buf.length ( the buffer is already big )
445: // or we already have space XXX
446: if (desiredSize <= buff.length) {
447: return;
448: }
449: // grow in larger chunks
450: if (desiredSize < 2 * buff.length) {
451: newSize = buff.length * 2;
452: if (limit > 0 && newSize > limit)
453: newSize = limit;
454: tmp = new char[newSize];
455: } else {
456: newSize = buff.length * 2 + count;
457: if (limit > 0 && newSize > limit)
458: newSize = limit;
459: tmp = new char[newSize];
460: }
461:
462: System.arraycopy(buff, start, tmp, start, end - start);
463: buff = tmp;
464: tmp = null;
465: }
466:
467: // -------------------- Conversion and getters --------------------
468:
469: public String toString() {
470: if (buff == null)
471: return null;
472: return new String(buff, start, end - start);
473: }
474:
475: public int getInt() {
476: return Ascii.parseInt(buff, start, end - start);
477: }
478:
479: // -------------------- equals --------------------
480:
481: /**
482: * Compares the message bytes to the specified String object.
483: * @param s the String to compare
484: * @return true if the comparison succeeded, false otherwise
485: */
486: public boolean equals(String s) {
487: char[] c = buff;
488: int len = end - start;
489: if (c == null || len != s.length()) {
490: return false;
491: }
492: int off = start;
493: for (int i = 0; i < len; i++) {
494: if (c[off++] != s.charAt(i)) {
495: return false;
496: }
497: }
498: return true;
499: }
500:
501: /**
502: * Compares the message bytes to the specified String object.
503: * @param s the String to compare
504: * @return true if the comparison succeeded, false otherwise
505: */
506: public boolean equalsIgnoreCase(String s) {
507: char[] c = buff;
508: int len = end - start;
509: if (c == null || len != s.length()) {
510: return false;
511: }
512: int off = start;
513: for (int i = 0; i < len; i++) {
514: if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
515: return false;
516: }
517: }
518: return true;
519: }
520:
521: public boolean equals(CharChunk cc) {
522: return equals(cc.getChars(), cc.getOffset(), cc.getLength());
523: }
524:
525: public boolean equals(char b2[], int off2, int len2) {
526: char b1[] = buff;
527: if (b1 == null && b2 == null)
528: return true;
529:
530: if (b1 == null || b2 == null || end - start != len2) {
531: return false;
532: }
533: int off1 = start;
534: int len = end - start;
535: while (len-- > 0) {
536: if (b1[off1++] != b2[off2++]) {
537: return false;
538: }
539: }
540: return true;
541: }
542:
543: public boolean equals(byte b2[], int off2, int len2) {
544: char b1[] = buff;
545: if (b2 == null && b1 == null)
546: return true;
547:
548: if (b1 == null || b2 == null || end - start != len2) {
549: return false;
550: }
551: int off1 = start;
552: int len = end - start;
553:
554: while (len-- > 0) {
555: if (b1[off1++] != (char) b2[off2++]) {
556: return false;
557: }
558: }
559: return true;
560: }
561:
562: /**
563: * Returns true if the message bytes starts with the specified string.
564: * @param s the string
565: */
566: public boolean startsWith(String s) {
567: char[] c = buff;
568: int len = s.length();
569: if (c == null || len > end - start) {
570: return false;
571: }
572: int off = start;
573: for (int i = 0; i < len; i++) {
574: if (c[off++] != s.charAt(i)) {
575: return false;
576: }
577: }
578: return true;
579: }
580:
581: /**
582: * Returns true if the message bytes starts with the specified string.
583: * @param s the string
584: */
585: public boolean startsWithIgnoreCase(String s, int pos) {
586: char[] c = buff;
587: int len = s.length();
588: if (c == null || len + pos > end - start) {
589: return false;
590: }
591: int off = start + pos;
592: for (int i = 0; i < len; i++) {
593: if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
594: return false;
595: }
596: }
597: return true;
598: }
599:
600: // -------------------- Hash code --------------------
601:
602: // normal hash.
603: public int hash() {
604: int code = 0;
605: for (int i = start; i < start + end - start; i++) {
606: code = code * 37 + buff[i];
607: }
608: return code;
609: }
610:
611: // hash ignoring case
612: public int hashIgnoreCase() {
613: int code = 0;
614: for (int i = start; i < end; i++) {
615: code = code * 37 + Ascii.toLower(buff[i]);
616: }
617: return code;
618: }
619:
620: public int indexOf(char c) {
621: return indexOf(c, start);
622: }
623:
624: /**
625: * Returns true if the message bytes starts with the specified string.
626: * @param s the string
627: */
628: public int indexOf(char c, int starting) {
629: int ret = indexOf(buff, start + starting, end, c);
630: return (ret >= start) ? ret - start : -1;
631: }
632:
633: public static int indexOf(char chars[], int off, int cend, char qq) {
634: while (off < cend) {
635: char b = chars[off];
636: if (b == qq)
637: return off;
638: off++;
639: }
640: return -1;
641: }
642:
643: public int indexOf(String src, int srcOff, int srcLen, int myOff) {
644: char first = src.charAt(srcOff);
645:
646: // Look for first char
647: int srcEnd = srcOff + srcLen;
648:
649: for (int i = myOff + start; i <= (end - srcLen); i++) {
650: if (buff[i] != first)
651: continue;
652: // found first char, now look for a match
653: int myPos = i + 1;
654: for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
655: if (buff[myPos++] != src.charAt(srcPos++))
656: break;
657: if (srcPos == srcEnd)
658: return i - start; // found it
659: }
660: }
661: return -1;
662: }
663:
664: // -------------------- utils
665: private int min(int a, int b) {
666: if (a < b)
667: return a;
668: return b;
669: }
670:
671: }
|