1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.tomcat.util.buf;
19
20 import java.text;
21 import java.util;
22 import java.io.Serializable;
23 import java.io.IOException;
24
25 /**
26 * This class is used to represent a subarray of bytes in an HTTP message.
27 * It represents all request/response elements. The byte/char conversions are
28 * delayed and cached. Everything is recyclable.
29 *
30 * The object can represent a byte[], a char[], or a (sub) String. All
31 * operations can be made in case sensitive mode or not.
32 *
33 * @author dac@eng.sun.com
34 * @author James Todd [gonzo@eng.sun.com]
35 * @author Costin Manolache
36 */
37 public final class MessageBytes implements Cloneable, Serializable {
38 // primary type ( whatever is set as original value )
39 private int type = T_NULL;
40
41 public static final int T_NULL = 0;
42 /** getType() is T_STR if the the object used to create the MessageBytes
43 was a String */
44 public static final int T_STR = 1;
45 /** getType() is T_STR if the the object used to create the MessageBytes
46 was a byte[] */
47 public static final int T_BYTES = 2;
48 /** getType() is T_STR if the the object used to create the MessageBytes
49 was a char[] */
50 public static final int T_CHARS = 3;
51
52 private int hashCode=0;
53 // did we computed the hashcode ?
54 private boolean hasHashCode=false;
55
56 // Is the represented object case sensitive ?
57 private boolean caseSensitive=true;
58
59 // Internal objects to represent array + offset, and specific methods
60 private ByteChunk byteC=new ByteChunk();
61 private CharChunk charC=new CharChunk();
62
63 // String
64 private String strValue;
65 // true if a String value was computed. Probably not needed,
66 // strValue!=null is the same
67 private boolean hasStrValue=false;
68
69 /**
70 * Creates a new, uninitialized MessageBytes object.
71 * @deprecated Use static newInstance() in order to allow
72 * future hooks.
73 */
74 public MessageBytes() {
75 }
76
77 /** Construct a new MessageBytes instance
78 */
79 public static MessageBytes newInstance() {
80 return factory.newInstance();
81 }
82
83 /** Configure the case sensitivity
84 */
85 public void setCaseSenitive( boolean b ) {
86 caseSensitive=b;
87 }
88
89 public MessageBytes getClone() {
90 try {
91 return (MessageBytes)this.clone();
92 } catch( Exception ex) {
93 return null;
94 }
95 }
96
97 public boolean isNull() {
98 // should we check also hasStrValue ???
99 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 /**
123 * Sets the content to the specified subarray of bytes.
124 *
125 * @param b the bytes
126 * @param off the start offset of the bytes
127 * @param len the length of the bytes
128 */
129 public void setBytes(byte[] b, int off, int len) {
130 byteC.setBytes( b, off, len );
131 type=T_BYTES;
132 hasStrValue=false;
133 hasHashCode=false;
134 hasIntValue=false;
135 hasLongValue=false;
136 hasDateValue=false;
137 }
138
139 /** Set the encoding. If the object was constructed from bytes[]. any
140 * previous conversion is reset.
141 * If no encoding is set, we'll use 8859-1.
142 */
143 public void setEncoding( String enc ) {
144 if( !byteC.isNull() ) {
145 // if the encoding changes we need to reset the converion results
146 charC.recycle();
147 hasStrValue=false;
148 }
149 byteC.setEncoding(enc);
150 }
151
152 /**
153 * Sets the content to be a char[]
154 *
155 * @param c the bytes
156 * @param off the start offset of the bytes
157 * @param len the length of the bytes
158 */
159 public void setChars( char[] c, int off, int len ) {
160 charC.setChars( c, off, len );
161 type=T_CHARS;
162 hasStrValue=false;
163 hasHashCode=false;
164 hasIntValue=false;
165 hasLongValue=false;
166 hasDateValue=false;
167 }
168
169 /** Remove the cached string value. Use it after a conversion on the
170 * byte[] or after the encoding is changed
171 * XXX Is this needed ?
172 */
173 public void resetStringValue() {
174 if( type != T_STR ) {
175 // If this was cread as a byte[] or char[], we remove
176 // the old string value
177 hasStrValue=false;
178 strValue=null;
179 }
180 }
181
182 /**
183 * Set the content to be a string
184 */
185 public void setString( String s ) {
186 strValue=s;
187 hasHashCode=false;
188 hasIntValue=false;
189 hasLongValue=false;
190 hasDateValue=false;
191 if (s == null) {
192 hasStrValue=false;
193 type=T_NULL;
194 } else {
195 hasStrValue=true;
196 type=T_STR;
197 }
198 }
199
200 // -------------------- Conversion and getters --------------------
201
202 /** Compute the string value
203 */
204 public String toString() {
205 if( hasStrValue ) 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 /**
282 * Returns the length of the original buffer.
283 * Note that the length in bytes may be different from the length
284 * in chars.
285 */
286 public int getLength() {
287 if(type==T_BYTES)
288 return byteC.getLength();
289 if(type==T_CHARS) {
290 return charC.getLength();
291 }
292 if(type==T_STR)
293 return strValue.length();
294 toString();
295 if( strValue==null ) 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) return false;
312 return strValue.equals( s );
313 case T_CHARS:
314 return charC.equals( s );
315 case T_BYTES:
316 return byteC.equals( s );
317 default:
318 return false;
319 }
320 }
321
322 /**
323 * Compares the message bytes to the specified String object.
324 * @param s the String to compare
325 * @return true if the comparison succeeded, false otherwise
326 */
327 public boolean equalsIgnoreCase(String s) {
328 switch (type) {
329 case T_STR:
330 if( strValue==null && s!=null) return false;
331 return strValue.equalsIgnoreCase( s );
332 case T_CHARS:
333 return charC.equalsIgnoreCase( s );
334 case T_BYTES:
335 return byteC.equalsIgnoreCase( s );
336 default:
337 return false;
338 }
339 }
340
341 public boolean equals(MessageBytes mb) {
342 switch (type) {
343 case T_STR:
344 return mb.equals( strValue );
345 }
346
347 if( mb.type != T_CHARS &&
348 mb.type!= T_BYTES ) {
349 // it's a string or int/date string value
350 return equals( mb.toString() );
351 }
352
353 // mb is either CHARS or BYTES.
354 // this is either CHARS or BYTES
355 // Deal with the 4 cases ( in fact 3, one is simetric)
356
357 if( mb.type == T_CHARS && type==T_CHARS ) {
358 return charC.equals( mb.charC );
359 }
360 if( mb.type==T_BYTES && type== T_BYTES ) {
361 return byteC.equals( mb.byteC );
362 }
363 if( mb.type== T_CHARS && type== T_BYTES ) {
364 return byteC.equals( mb.charC );
365 }
366 if( mb.type== T_BYTES && type== T_CHARS ) {
367 return mb.byteC.equals( charC );
368 }
369 // can't happen
370 return true;
371 }
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 ) return false;
400 if( strValue.length() < pos + s.length() ) return false;
401
402 for( int i=0; i<s.length(); i++ ) {
403 if( Ascii.toLower( s.charAt( i ) ) !=
404 Ascii.toLower( strValue.charAt( pos + i ))) {
405 return false;
406 }
407 }
408 return true;
409 case T_CHARS:
410 return charC.startsWithIgnoreCase( s, pos );
411 case T_BYTES:
412 return byteC.startsWithIgnoreCase( s, pos );
413 default:
414 return false;
415 }
416 }
417
418
419
420 // -------------------- Hash code --------------------
421 public int hashCode() {
422 if( hasHashCode ) return hashCode;
423 int code = 0;
424
425 if( caseSensitive )
426 code=hash();
427 else
428 code=hashIgnoreCase();
429 hashCode=code;
430 hasHashCode=true;
431 return code;
432 }
433
434 // normal hash.
435 private int hash() {
436 int code=0;
437 switch (type) {
438 case T_STR:
439 // We need to use the same hash function
440 for (int i = 0; i < strValue.length(); i++) {
441 code = code * 37 + strValue.charAt( i );
442 }
443 return code;
444 case T_CHARS:
445 return charC.hash();
446 case T_BYTES:
447 return byteC.hash();
448 default:
449 return 0;
450 }
451 }
452
453 // hash ignoring case
454 private int hashIgnoreCase() {
455 int code=0;
456 switch (type) {
457 case T_STR:
458 for (int i = 0; i < strValue.length(); i++) {
459 code = code * 37 + Ascii.toLower(strValue.charAt( i ));
460 }
461 return code;
462 case T_CHARS:
463 return charC.hashIgnoreCase();
464 case T_BYTES:
465 return byteC.hashIgnoreCase();
466 default:
467 return 0;
468 }
469 }
470
471 public int indexOf(char c) {
472 return indexOf( c, 0);
473 }
474
475 // Inefficient initial implementation. Will be replaced on the next
476 // round of tune-up
477 public int indexOf(String s, int starting) {
478 toString();
479 return strValue.indexOf( s, starting );
480 }
481
482 // Inefficient initial implementation. Will be replaced on the next
483 // round of tune-up
484 public int indexOf(String s) {
485 return indexOf( s, 0 );
486 }
487
488 public int indexOfIgnoreCase(String s, int starting) {
489 toString();
490 String upper=strValue.toUpperCase();
491 String sU=s.toUpperCase();
492 return upper.indexOf( sU, starting );
493 }
494
495 /**
496 * Returns true if the message bytes starts with the specified string.
497 * @param c the character
498 * @param starting The start position
499 */
500 public int indexOf(char c, int starting) {
501 switch (type) {
502 case T_STR:
503 return strValue.indexOf( c, starting );
504 case T_CHARS:
505 return charC.indexOf( c, starting);
506 case T_BYTES:
507 return byteC.indexOf( c, starting );
508 default:
509 return -1;
510 }
511 }
512
513 /** Copy the src into this MessageBytes, allocating more space if
514 * needed
515 */
516 public void duplicate( MessageBytes src ) throws IOException
517 {
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 {
667 if( hasDateValue ) {
668 if( dateValue==null) 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
682 // Used for headers conversion
683 /** Convert the buffer to an int, cache the value
684 */
685 public int getInt()
686 {
687 if( hasIntValue )
688 return intValue;
689
690 switch (type) {
691 case T_BYTES:
692 intValue=byteC.getInt();
693 break;
694 default:
695 intValue=Integer.parseInt(toString());
696 }
697 hasIntValue=true;
698 return intValue;
699 }
700
701 // Used for headers conversion
702 /** Convert the buffer to an long, cache the value
703 */
704 public long getLong() {
705 if( hasLongValue )
706 return longValue;
707
708 switch (type) {
709 case T_BYTES:
710 longValue=byteC.getLong();
711 break;
712 default:
713 longValue=Long.parseLong(toString());
714 }
715
716 hasLongValue=true;
717 return longValue;
718
719 }
720
721 // -------------------- Future may be different --------------------
722
723 private static MessageBytesFactory factory=new MessageBytesFactory();
724
725 public static void setFactory( MessageBytesFactory mbf ) {
726 factory=mbf;
727 }
728
729 public static class MessageBytesFactory {
730 protected MessageBytesFactory() {
731 }
732 public MessageBytes newInstance() {
733 return new MessageBytes();
734 }
735 }
736 }