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