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