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.text.*;
021: import java.util.*;
022: import java.io.Serializable;
023: import java.io.IOException;
024:
025: /**
026: * This class is used to represent a subarray of bytes in an HTTP message.
027: * It represents all request/response elements. The byte/char conversions are
028: * delayed and cached. Everything is recyclable.
029: *
030: * The object can represent a byte[], a char[], or a (sub) String. All
031: * operations can be made in case sensitive mode or not.
032: *
033: * @author dac@eng.sun.com
034: * @author James Todd [gonzo@eng.sun.com]
035: * @author Costin Manolache
036: */
037: public final class MessageBytes implements Cloneable, Serializable {
038: // primary type ( whatever is set as original value )
039: private int type = T_NULL;
040:
041: public static final int T_NULL = 0;
042: /** getType() is T_STR if the the object used to create the MessageBytes
043: was a String */
044: public static final int T_STR = 1;
045: /** getType() is T_STR if the the object used to create the MessageBytes
046: was a byte[] */
047: public static final int T_BYTES = 2;
048: /** getType() is T_STR if the the object used to create the MessageBytes
049: was a char[] */
050: public static final int T_CHARS = 3;
051:
052: private int hashCode = 0;
053: // did we computed the hashcode ?
054: private boolean hasHashCode = false;
055:
056: // Is the represented object case sensitive ?
057: private boolean caseSensitive = true;
058:
059: // Internal objects to represent array + offset, and specific methods
060: private ByteChunk byteC = new ByteChunk();
061: private CharChunk charC = new CharChunk();
062:
063: // String
064: private String strValue;
065: // true if a String value was computed. Probably not needed,
066: // strValue!=null is the same
067: private boolean hasStrValue = false;
068:
069: /**
070: * Creates a new, uninitialized MessageBytes object.
071: * @deprecated Use static newInstance() in order to allow
072: * future hooks.
073: */
074: public MessageBytes() {
075: }
076:
077: /** Construct a new MessageBytes instance
078: */
079: public static MessageBytes newInstance() {
080: return factory.newInstance();
081: }
082:
083: /** Configure the case sensitivity
084: */
085: public void setCaseSenitive(boolean b) {
086: caseSensitive = b;
087: }
088:
089: public MessageBytes getClone() {
090: try {
091: return (MessageBytes) this .clone();
092: } catch (Exception ex) {
093: return null;
094: }
095: }
096:
097: public boolean isNull() {
098: // should we check also hasStrValue ???
099: return byteC.isNull() && charC.isNull() && !hasStrValue;
100: // bytes==null && strValue==null;
101: }
102:
103: /**
104: * Resets the message bytes to an uninitialized (NULL) state.
105: */
106: public void recycle() {
107: type = T_NULL;
108: byteC.recycle();
109: charC.recycle();
110:
111: strValue = null;
112: caseSensitive = true;
113:
114: hasStrValue = false;
115: hasHashCode = false;
116: hasIntValue = false;
117: hasLongValue = false;
118: hasDateValue = false;
119: }
120:
121: /**
122: * Sets the content to the specified subarray of bytes.
123: *
124: * @param b the bytes
125: * @param off the start offset of the bytes
126: * @param len the length of the bytes
127: */
128: public void setBytes(byte[] b, int off, int len) {
129: byteC.setBytes(b, off, len);
130: type = T_BYTES;
131: hasStrValue = false;
132: hasHashCode = false;
133: hasIntValue = false;
134: hasLongValue = false;
135: hasDateValue = false;
136: }
137:
138: /** Set the encoding. If the object was constructed from bytes[]. any
139: * previous conversion is reset.
140: * If no encoding is set, we'll use 8859-1.
141: */
142: public void setEncoding(String enc) {
143: if (!byteC.isNull()) {
144: // if the encoding changes we need to reset the converion results
145: charC.recycle();
146: hasStrValue = false;
147: }
148: byteC.setEncoding(enc);
149: }
150:
151: /**
152: * Sets the content to be a char[]
153: *
154: * @param c the bytes
155: * @param off the start offset of the bytes
156: * @param len the length of the bytes
157: */
158: public void setChars(char[] c, int off, int len) {
159: charC.setChars(c, off, len);
160: type = T_CHARS;
161: hasStrValue = false;
162: hasHashCode = false;
163: hasIntValue = false;
164: hasLongValue = false;
165: hasDateValue = false;
166: }
167:
168: /** Remove the cached string value. Use it after a conversion on the
169: * byte[] or after the encoding is changed
170: * XXX Is this needed ?
171: */
172: public void resetStringValue() {
173: if (type != T_STR) {
174: // If this was cread as a byte[] or char[], we remove
175: // the old string value
176: hasStrValue = false;
177: strValue = null;
178: }
179: }
180:
181: /**
182: * Set the content to be a string
183: */
184: public void setString(String s) {
185: strValue = s;
186: hasHashCode = false;
187: hasIntValue = false;
188: hasLongValue = false;
189: hasDateValue = false;
190: if (s == null) {
191: hasStrValue = false;
192: type = T_NULL;
193: } else {
194: hasStrValue = true;
195: type = T_STR;
196: }
197: }
198:
199: // -------------------- Conversion and getters --------------------
200:
201: /** Compute the string value
202: */
203: public String toString() {
204: if (hasStrValue)
205: return strValue;
206:
207: switch (type) {
208: case T_CHARS:
209: strValue = charC.toString();
210: hasStrValue = true;
211: return strValue;
212: case T_BYTES:
213: strValue = byteC.toString();
214: hasStrValue = true;
215: return strValue;
216: }
217: return null;
218: }
219:
220: //----------------------------------------
221: /** Return the type of the original content. Can be
222: * T_STR, T_BYTES, T_CHARS or T_NULL
223: */
224: public int getType() {
225: return type;
226: }
227:
228: /**
229: * Returns the byte chunk, representing the byte[] and offset/length.
230: * Valid only if T_BYTES or after a conversion was made.
231: */
232: public ByteChunk getByteChunk() {
233: return byteC;
234: }
235:
236: /**
237: * Returns the char chunk, representing the char[] and offset/length.
238: * Valid only if T_CHARS or after a conversion was made.
239: */
240: public CharChunk getCharChunk() {
241: return charC;
242: }
243:
244: /**
245: * Returns the string value.
246: * Valid only if T_STR or after a conversion was made.
247: */
248: public String getString() {
249: return strValue;
250: }
251:
252: /** Unimplemented yet. Do a char->byte conversion.
253: */
254: public void toBytes() {
255: if (!byteC.isNull()) {
256: type = T_BYTES;
257: return;
258: }
259: toString();
260: type = T_BYTES;
261: byte bb[] = strValue.getBytes();
262: byteC.setBytes(bb, 0, bb.length);
263: }
264:
265: /** Convert to char[] and fill the CharChunk.
266: * XXX Not optimized - it converts to String first.
267: */
268: public void toChars() {
269: if (!charC.isNull()) {
270: type = T_CHARS;
271: return;
272: }
273: // inefficient
274: toString();
275: type = T_CHARS;
276: char cc[] = strValue.toCharArray();
277: charC.setChars(cc, 0, cc.length);
278: }
279:
280: /**
281: * Returns the length of the original buffer.
282: * Note that the length in bytes may be different from the length
283: * in chars.
284: */
285: public int getLength() {
286: if (type == T_BYTES)
287: return byteC.getLength();
288: if (type == T_CHARS) {
289: return charC.getLength();
290: }
291: if (type == T_STR)
292: return strValue.length();
293: toString();
294: if (strValue == null)
295: return 0;
296: return strValue.length();
297: }
298:
299: // -------------------- equals --------------------
300:
301: /**
302: * Compares the message bytes to the specified String object.
303: * @param s the String to compare
304: * @return true if the comparison succeeded, false otherwise
305: */
306: public boolean equals(String s) {
307: if (!caseSensitive)
308: return equalsIgnoreCase(s);
309: switch (type) {
310: case T_STR:
311: if (strValue == null && s != null)
312: return false;
313: return strValue.equals(s);
314: case T_CHARS:
315: return charC.equals(s);
316: case T_BYTES:
317: return byteC.equals(s);
318: default:
319: return false;
320: }
321: }
322:
323: /**
324: * Compares the message bytes to the specified String object.
325: * @param s the String to compare
326: * @return true if the comparison succeeded, false otherwise
327: */
328: public boolean equalsIgnoreCase(String s) {
329: switch (type) {
330: case T_STR:
331: if (strValue == null && s != null)
332: return false;
333: return strValue.equalsIgnoreCase(s);
334: case T_CHARS:
335: return charC.equalsIgnoreCase(s);
336: case T_BYTES:
337: return byteC.equalsIgnoreCase(s);
338: default:
339: return false;
340: }
341: }
342:
343: public boolean equals(MessageBytes mb) {
344: switch (type) {
345: case T_STR:
346: return mb.equals(strValue);
347: }
348:
349: if (mb.type != T_CHARS && mb.type != T_BYTES) {
350: // it's a string or int/date string value
351: return equals(mb.toString());
352: }
353:
354: // mb is either CHARS or BYTES.
355: // this is either CHARS or BYTES
356: // Deal with the 4 cases ( in fact 3, one is simetric)
357:
358: if (mb.type == T_CHARS && type == T_CHARS) {
359: return charC.equals(mb.charC);
360: }
361: if (mb.type == T_BYTES && type == T_BYTES) {
362: return byteC.equals(mb.byteC);
363: }
364: if (mb.type == T_CHARS && type == T_BYTES) {
365: return byteC.equals(mb.charC);
366: }
367: if (mb.type == T_BYTES && type == T_CHARS) {
368: return mb.byteC.equals(charC);
369: }
370: // can't happen
371: return true;
372: }
373:
374: /**
375: * Returns true if the message bytes starts with the specified string.
376: * @param s the string
377: */
378: public boolean startsWith(String s) {
379: switch (type) {
380: case T_STR:
381: return strValue.startsWith(s);
382: case T_CHARS:
383: return charC.startsWith(s);
384: case T_BYTES:
385: return byteC.startsWith(s);
386: default:
387: return false;
388: }
389: }
390:
391: /**
392: * Returns true if the message bytes starts with the specified string.
393: * @param s the string
394: * @param pos The start position
395: */
396: public boolean startsWithIgnoreCase(String s, int pos) {
397: switch (type) {
398: case T_STR:
399: if (strValue == null)
400: return false;
401: if (strValue.length() < pos + s.length())
402: return false;
403:
404: for (int i = 0; i < s.length(); i++) {
405: if (Ascii.toLower(s.charAt(i)) != Ascii
406: .toLower(strValue.charAt(pos + i))) {
407: return false;
408: }
409: }
410: return true;
411: case T_CHARS:
412: return charC.startsWithIgnoreCase(s, pos);
413: case T_BYTES:
414: return byteC.startsWithIgnoreCase(s, pos);
415: default:
416: return false;
417: }
418: }
419:
420: // -------------------- Hash code --------------------
421: public int hashCode() {
422: if (hasHashCode)
423: return hashCode;
424: int code = 0;
425:
426: if (caseSensitive)
427: code = hash();
428: else
429: code = hashIgnoreCase();
430: hashCode = code;
431: hasHashCode = true;
432: return code;
433: }
434:
435: // normal hash.
436: private int hash() {
437: int code = 0;
438: switch (type) {
439: case T_STR:
440: // We need to use the same hash function
441: for (int i = 0; i < strValue.length(); i++) {
442: code = code * 37 + strValue.charAt(i);
443: }
444: return code;
445: case T_CHARS:
446: return charC.hash();
447: case T_BYTES:
448: return byteC.hash();
449: default:
450: return 0;
451: }
452: }
453:
454: // hash ignoring case
455: private int hashIgnoreCase() {
456: int code = 0;
457: switch (type) {
458: case T_STR:
459: for (int i = 0; i < strValue.length(); i++) {
460: code = code * 37 + Ascii.toLower(strValue.charAt(i));
461: }
462: return code;
463: case T_CHARS:
464: return charC.hashIgnoreCase();
465: case T_BYTES:
466: return byteC.hashIgnoreCase();
467: default:
468: return 0;
469: }
470: }
471:
472: public int indexOf(char c) {
473: return indexOf(c, 0);
474: }
475:
476: // Inefficient initial implementation. Will be replaced on the next
477: // round of tune-up
478: public int indexOf(String s, int starting) {
479: toString();
480: return strValue.indexOf(s, starting);
481: }
482:
483: // Inefficient initial implementation. Will be replaced on the next
484: // round of tune-up
485: public int indexOf(String s) {
486: return indexOf(s, 0);
487: }
488:
489: public int indexOfIgnoreCase(String s, int starting) {
490: toString();
491: String upper = strValue.toUpperCase();
492: String sU = s.toUpperCase();
493: return upper.indexOf(sU, starting);
494: }
495:
496: /**
497: * Returns true if the message bytes starts with the specified string.
498: * @param c the character
499: * @param starting The start position
500: */
501: public int indexOf(char c, int starting) {
502: switch (type) {
503: case T_STR:
504: return strValue.indexOf(c, starting);
505: case T_CHARS:
506: return charC.indexOf(c, starting);
507: case T_BYTES:
508: return byteC.indexOf(c, starting);
509: default:
510: return -1;
511: }
512: }
513:
514: /** Copy the src into this MessageBytes, allocating more space if
515: * needed
516: */
517: public void duplicate(MessageBytes src) throws IOException {
518: switch (src.getType()) {
519: case MessageBytes.T_BYTES:
520: type = T_BYTES;
521: ByteChunk bc = src.getByteChunk();
522: byteC.allocate(2 * bc.getLength(), -1);
523: byteC.append(bc);
524: break;
525: case MessageBytes.T_CHARS:
526: type = T_CHARS;
527: CharChunk cc = src.getCharChunk();
528: charC.allocate(2 * cc.getLength(), -1);
529: charC.append(cc);
530: break;
531: case MessageBytes.T_STR:
532: type = T_STR;
533: String sc = src.getString();
534: this .setString(sc);
535: break;
536: }
537: }
538:
539: // -------------------- Deprecated code --------------------
540: // efficient int, long and date
541: // XXX used only for headers - shouldn't be
542: // stored here.
543: private int intValue;
544: private boolean hasIntValue = false;
545: private long longValue;
546: private boolean hasLongValue = false;
547: private Date dateValue;
548: private boolean hasDateValue = false;
549:
550: /**
551: * @deprecated The buffer are general purpose, caching for headers should
552: * be done in headers. The second parameter allows us to pass a date format
553: * instance to avoid synchronization problems.
554: */
555: public void setTime(long t, DateFormat df) {
556: // XXX replace it with a byte[] tool
557: recycle();
558: if (dateValue == null)
559: dateValue = new Date(t);
560: else
561: dateValue.setTime(t);
562: if (df == null)
563: strValue = DateTool.format1123(dateValue);
564: else
565: strValue = DateTool.format1123(dateValue, df);
566: hasStrValue = true;
567: hasDateValue = true;
568: type = T_STR;
569: }
570:
571: public void setTime(long t) {
572: setTime(t, null);
573: }
574:
575: /** Set the buffer to the representation of an int
576: */
577: public void setInt(int i) {
578: byteC.allocate(16, 32);
579: int current = i;
580: byte[] buf = byteC.getBuffer();
581: int start = 0;
582: int end = 0;
583: if (i == 0) {
584: buf[end++] = (byte) '0';
585: }
586: if (i < 0) {
587: current = -i;
588: buf[end++] = (byte) '-';
589: }
590: while (current > 0) {
591: int digit = current % 10;
592: current = current / 10;
593: buf[end++] = HexUtils.HEX[digit];
594: }
595: byteC.setOffset(0);
596: byteC.setEnd(end);
597: // Inverting buffer
598: end--;
599: if (i < 0) {
600: start++;
601: }
602: while (end > start) {
603: byte temp = buf[start];
604: buf[start] = buf[end];
605: buf[end] = temp;
606: start++;
607: end--;
608: }
609: intValue = i;
610: hasStrValue = false;
611: hasHashCode = false;
612: hasIntValue = true;
613: hasLongValue = false;
614: hasDateValue = false;
615: type = T_BYTES;
616: }
617:
618: /** Set the buffer to the representation of an long
619: */
620: public void setLong(long l) {
621: byteC.allocate(32, 64);
622: long current = l;
623: byte[] buf = byteC.getBuffer();
624: int start = 0;
625: int end = 0;
626: if (l == 0) {
627: buf[end++] = (byte) '0';
628: }
629: if (l < 0) {
630: current = -l;
631: buf[end++] = (byte) '-';
632: }
633: while (current > 0) {
634: int digit = (int) (current % 10);
635: current = current / 10;
636: buf[end++] = HexUtils.HEX[digit];
637: }
638: byteC.setOffset(0);
639: byteC.setEnd(end);
640: // Inverting buffer
641: end--;
642: if (l < 0) {
643: start++;
644: }
645: while (end > start) {
646: byte temp = buf[start];
647: buf[start] = buf[end];
648: buf[end] = temp;
649: start++;
650: end--;
651: }
652: longValue = l;
653: hasStrValue = false;
654: hasHashCode = false;
655: hasIntValue = false;
656: hasLongValue = true;
657: hasDateValue = false;
658: type = T_BYTES;
659: }
660:
661: /**
662: * @deprecated The buffer are general purpose, caching for headers should
663: * be done in headers
664: */
665: public long getTime() {
666: if (hasDateValue) {
667: if (dateValue == null)
668: return -1;
669: return dateValue.getTime();
670: }
671:
672: long l = DateTool.parseDate(this );
673: if (dateValue == null)
674: dateValue = new Date(l);
675: else
676: dateValue.setTime(l);
677: hasDateValue = true;
678: return l;
679: }
680:
681: // Used for headers conversion
682: /** Convert the buffer to an int, cache the value
683: */
684: public int getInt() {
685: if (hasIntValue)
686: return intValue;
687:
688: switch (type) {
689: case T_BYTES:
690: intValue = byteC.getInt();
691: break;
692: default:
693: intValue = Integer.parseInt(toString());
694: }
695: hasIntValue = true;
696: return intValue;
697: }
698:
699: // Used for headers conversion
700: /** Convert the buffer to an long, cache the value
701: */
702: public long getLong() {
703: if (hasLongValue)
704: return longValue;
705:
706: switch (type) {
707: case T_BYTES:
708: longValue = byteC.getLong();
709: break;
710: default:
711: longValue = Long.parseLong(toString());
712: }
713:
714: hasLongValue = true;
715: return longValue;
716:
717: }
718:
719: // -------------------- Future may be different --------------------
720:
721: private static MessageBytesFactory factory = new MessageBytesFactory();
722:
723: public static void setFactory(MessageBytesFactory mbf) {
724: factory = mbf;
725: }
726:
727: public static class MessageBytesFactory {
728: protected MessageBytesFactory() {
729: }
730:
731: public MessageBytes newInstance() {
732: return new MessageBytes();
733: }
734: }
735: }
|