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