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 package org.apache.tomcat.util.buf;
18
19 import java.io.IOException;
20 import java.io.Serializable;
21 import java.nio.ByteBuffer;
22 import java.nio.charset.Charset;
23 import java.util.Locale;
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 private static final long serialVersionUID = 1L;
39
40 // primary type ( whatever is set as original value )
41 private int type = T_NULL;
42
43 public static final int T_NULL = 0;
44 /** getType() is T_STR if the the object used to create the MessageBytes
45 was a String */
46 public static final int T_STR = 1;
47 /** getType() is T_BYTES if the the object used to create the MessageBytes
48 was a byte[] */
49 public static final int T_BYTES = 2;
50 /** getType() is T_CHARS if the the object used to create the MessageBytes
51 was a char[] */
52 public static final int T_CHARS = 3;
53
54 private int hashCode=0;
55 // did we compute the hashcode ?
56 private boolean hasHashCode=false;
57
58 // Internal objects to represent array + offset, and specific methods
59 private final ByteChunk byteC=new ByteChunk();
60 private final CharChunk charC=new CharChunk();
61
62 // String
63 private String strValue;
64 // true if a String value was computed. Probably not needed,
65 // strValue!=null is the same
66 private boolean hasStrValue=false;
67
68 /**
69 * Creates a new, uninitialized MessageBytes object.
70 * Use static newInstance() in order to allow
71 * future hooks.
72 */
73 private MessageBytes() {
74 }
75
76 /**
77 * Construct a new MessageBytes instance.
78 * @return the instance
79 */
80 public static MessageBytes newInstance() {
81 return factory.newInstance();
82 }
83
84 @Override
85 public Object clone() throws CloneNotSupportedException {
86 return super.clone();
87 }
88
89 public boolean isNull() {
90 return byteC.isNull() && charC.isNull() && !hasStrValue;
91 }
92
93 /**
94 * Resets the message bytes to an uninitialized (NULL) state.
95 */
96 public void recycle() {
97 type=T_NULL;
98 byteC.recycle();
99 charC.recycle();
100
101 strValue=null;
102
103 hasStrValue=false;
104 hasHashCode=false;
105 hasLongValue=false;
106 }
107
108
109 /**
110 * Sets the content to the specified subarray of bytes.
111 *
112 * @param b the bytes
113 * @param off the start offset of the bytes
114 * @param len the length of the bytes
115 */
116 public void setBytes(byte[] b, int off, int len) {
117 byteC.setBytes( b, off, len );
118 type=T_BYTES;
119 hasStrValue=false;
120 hasHashCode=false;
121 hasLongValue=false;
122 }
123
124 /**
125 * Sets the content to be a char[]
126 *
127 * @param c the chars
128 * @param off the start offset of the chars
129 * @param len the length of the chars
130 */
131 public void setChars( char[] c, int off, int len ) {
132 charC.setChars( c, off, len );
133 type=T_CHARS;
134 hasStrValue=false;
135 hasHashCode=false;
136 hasLongValue=false;
137 }
138
139 /**
140 * Set the content to be a string
141 * @param s The string
142 */
143 public void setString( String s ) {
144 strValue=s;
145 hasHashCode=false;
146 hasLongValue=false;
147 if (s == null) {
148 hasStrValue=false;
149 type=T_NULL;
150 } else {
151 hasStrValue=true;
152 type=T_STR;
153 }
154 }
155
156 // -------------------- Conversion and getters --------------------
157
158 /**
159 * Compute the string value.
160 * @return the string
161 */
162 @Override
163 public String toString() {
164 if (hasStrValue) {
165 return strValue;
166 }
167
168 switch (type) {
169 case T_CHARS:
170 strValue = charC.toString();
171 hasStrValue = true;
172 return strValue;
173 case T_BYTES:
174 strValue = byteC.toString();
175 hasStrValue = true;
176 return strValue;
177 }
178 return null;
179 }
180
181 //----------------------------------------
182 /**
183 * Return the type of the original content. Can be
184 * T_STR, T_BYTES, T_CHARS or T_NULL
185 * @return the type
186 */
187 public int getType() {
188 return type;
189 }
190
191 /**
192 * Returns the byte chunk, representing the byte[] and offset/length.
193 * Valid only if T_BYTES or after a conversion was made.
194 * @return the byte chunk
195 */
196 public ByteChunk getByteChunk() {
197 return byteC;
198 }
199
200 /**
201 * Returns the char chunk, representing the char[] and offset/length.
202 * Valid only if T_CHARS or after a conversion was made.
203 * @return the char chunk
204 */
205 public CharChunk getCharChunk() {
206 return charC;
207 }
208
209 /**
210 * Returns the string value.
211 * Valid only if T_STR or after a conversion was made.
212 * @return the string
213 */
214 public String getString() {
215 return strValue;
216 }
217
218 /**
219 * @return the Charset used for string<->byte conversions.
220 */
221 public Charset getCharset() {
222 return byteC.getCharset();
223 }
224
225 /**
226 * Set the Charset used for string<->byte conversions.
227 * @param charset The charset
228 */
229 public void setCharset(Charset charset) {
230 byteC.setCharset(charset);
231 }
232
233
234 /**
235 * Do a char->byte conversion.
236 */
237 public void toBytes() {
238 if (isNull()) {
239 return;
240 }
241 if (!byteC.isNull()) {
242 type = T_BYTES;
243 return;
244 }
245 toString();
246 type = T_BYTES;
247 Charset charset = byteC.getCharset();
248 ByteBuffer result = charset.encode(strValue);
249 byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
250 }
251
252
253 /**
254 * Convert to char[] and fill the CharChunk.
255 * XXX Not optimized - it converts to String first.
256 */
257 public void toChars() {
258 if (isNull()) {
259 return;
260 }
261 if (!charC.isNull()) {
262 type = T_CHARS;
263 return;
264 }
265 // inefficient
266 toString();
267 type = T_CHARS;
268 char cc[] = strValue.toCharArray();
269 charC.setChars(cc, 0, cc.length);
270 }
271
272
273 /**
274 * Returns the length of the original buffer.
275 * Note that the length in bytes may be different from the length
276 * in chars.
277 * @return the length
278 */
279 public int getLength() {
280 if(type==T_BYTES) {
281 return byteC.getLength();
282 }
283 if(type==T_CHARS) {
284 return charC.getLength();
285 }
286 if(type==T_STR) {
287 return strValue.length();
288 }
289 toString();
290 if( strValue==null ) {
291 return 0;
292 }
293 return strValue.length();
294 }
295
296 // -------------------- equals --------------------
297
298 /**
299 * Compares the message bytes to the specified String object.
300 * @param s the String to compare
301 * @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
302 */
303 public boolean equals(String s) {
304 switch (type) {
305 case T_STR:
306 if (strValue == null) {
307 return s == null;
308 }
309 return strValue.equals( s );
310 case T_CHARS:
311 return charC.equals( s );
312 case T_BYTES:
313 return byteC.equals( s );
314 default:
315 return false;
316 }
317 }
318
319 /**
320 * Compares the message bytes to the specified String object.
321 * @param s the String to compare
322 * @return <code>true</code> if the comparison succeeded, <code>false</code> otherwise
323 */
324 public boolean equalsIgnoreCase(String s) {
325 switch (type) {
326 case T_STR:
327 if (strValue == null) {
328 return s == null;
329 }
330 return strValue.equalsIgnoreCase( s );
331 case T_CHARS:
332 return charC.equalsIgnoreCase( s );
333 case T_BYTES:
334 return byteC.equalsIgnoreCase( s );
335 default:
336 return false;
337 }
338 }
339
340 @Override
341 public boolean equals(Object obj) {
342 if (obj instanceof MessageBytes) {
343 return equals((MessageBytes) obj);
344 }
345 return false;
346 }
347
348 public boolean equals(MessageBytes mb) {
349 switch (type) {
350 case T_STR:
351 return mb.equals( strValue );
352 }
353
354 if( mb.type != T_CHARS &&
355 mb.type!= T_BYTES ) {
356 // it's a string or int/date string value
357 return equals( mb.toString() );
358 }
359
360 // mb is either CHARS or BYTES.
361 // this is either CHARS or BYTES
362 // Deal with the 4 cases ( in fact 3, one is symmetric)
363
364 if( mb.type == T_CHARS && type==T_CHARS ) {
365 return charC.equals( mb.charC );
366 }
367 if( mb.type==T_BYTES && type== T_BYTES ) {
368 return byteC.equals( mb.byteC );
369 }
370 if( mb.type== T_CHARS && type== T_BYTES ) {
371 return byteC.equals( mb.charC );
372 }
373 if( mb.type== T_BYTES && type== T_CHARS ) {
374 return mb.byteC.equals( charC );
375 }
376 // can't happen
377 return true;
378 }
379
380
381 /**
382 * @return <code>true</code> if the message bytes starts with the specified string.
383 * @param s the string
384 * @param pos The start position
385 */
386 public boolean startsWithIgnoreCase(String s, int pos) {
387 switch (type) {
388 case T_STR:
389 if( strValue==null ) {
390 return false;
391 }
392 if( strValue.length() < pos + s.length() ) {
393 return false;
394 }
395
396 for( int i=0; i<s.length(); i++ ) {
397 if( Ascii.toLower( s.charAt( i ) ) !=
398 Ascii.toLower( strValue.charAt( pos + i ))) {
399 return false;
400 }
401 }
402 return true;
403 case T_CHARS:
404 return charC.startsWithIgnoreCase( s, pos );
405 case T_BYTES:
406 return byteC.startsWithIgnoreCase( s, pos );
407 default:
408 return false;
409 }
410 }
411
412
413 // -------------------- Hash code --------------------
414 @Override
415 public int hashCode() {
416 if( hasHashCode ) {
417 return hashCode;
418 }
419 int code = 0;
420
421 code=hash();
422 hashCode=code;
423 hasHashCode=true;
424 return code;
425 }
426
427 // normal hash.
428 private int hash() {
429 int code=0;
430 switch (type) {
431 case T_STR:
432 // We need to use the same hash function
433 for (int i = 0; i < strValue.length(); i++) {
434 code = code * 37 + strValue.charAt( i );
435 }
436 return code;
437 case T_CHARS:
438 return charC.hash();
439 case T_BYTES:
440 return byteC.hash();
441 default:
442 return 0;
443 }
444 }
445
446 // Inefficient initial implementation. Will be replaced on the next
447 // round of tune-up
448 public int indexOf(String s, int starting) {
449 toString();
450 return strValue.indexOf( s, starting );
451 }
452
453 // Inefficient initial implementation. Will be replaced on the next
454 // round of tune-up
455 public int indexOf(String s) {
456 return indexOf( s, 0 );
457 }
458
459 public int indexOfIgnoreCase(String s, int starting) {
460 toString();
461 String upper=strValue.toUpperCase(Locale.ENGLISH);
462 String sU=s.toUpperCase(Locale.ENGLISH);
463 return upper.indexOf( sU, starting );
464 }
465
466 /**
467 * Copy the src into this MessageBytes, allocating more space if needed.
468 * @param src The source
469 * @throws IOException Writing overflow data to the output channel failed
470 */
471 public void duplicate( MessageBytes src ) throws IOException
472 {
473 switch( src.getType() ) {
474 case MessageBytes.T_BYTES:
475 type=T_BYTES;
476 ByteChunk bc=src.getByteChunk();
477 byteC.allocate( 2 * bc.getLength(), -1 );
478 byteC.append( bc );
479 break;
480 case MessageBytes.T_CHARS:
481 type=T_CHARS;
482 CharChunk cc=src.getCharChunk();
483 charC.allocate( 2 * cc.getLength(), -1 );
484 charC.append( cc );
485 break;
486 case MessageBytes.T_STR:
487 type=T_STR;
488 String sc=src.getString();
489 this.setString( sc );
490 break;
491 }
492 setCharset(src.getCharset());
493 }
494
495 // -------------------- Deprecated code --------------------
496 // efficient long
497 // XXX used only for headers - shouldn't be stored here.
498 private long longValue;
499 private boolean hasLongValue=false;
500
501 /**
502 * Set the buffer to the representation of a long.
503 * @param l The long
504 */
505 public void setLong(long l) {
506 byteC.allocate(32, 64);
507 long current = l;
508 byte[] buf = byteC.getBuffer();
509 int start = 0;
510 int end = 0;
511 if (l == 0) {
512 buf[end++] = (byte) '0';
513 }
514 if (l < 0) {
515 current = -l;
516 buf[end++] = (byte) '-';
517 }
518 while (current > 0) {
519 int digit = (int) (current % 10);
520 current = current / 10;
521 buf[end++] = HexUtils.getHex(digit);
522 }
523 byteC.setOffset(0);
524 byteC.setEnd(end);
525 // Inverting buffer
526 end--;
527 if (l < 0) {
528 start++;
529 }
530 while (end > start) {
531 byte temp = buf[start];
532 buf[start] = buf[end];
533 buf[end] = temp;
534 start++;
535 end--;
536 }
537 longValue=l;
538 hasStrValue=false;
539 hasHashCode=false;
540 hasLongValue=true;
541 type=T_BYTES;
542 }
543
544 // Used for headers conversion
545 /**
546 * Convert the buffer to a long, cache the value.
547 * @return the long value
548 */
549 public long getLong() {
550 if( hasLongValue ) {
551 return longValue;
552 }
553
554 switch (type) {
555 case T_BYTES:
556 longValue=byteC.getLong();
557 break;
558 default:
559 longValue=Long.parseLong(toString());
560 }
561
562 hasLongValue=true;
563 return longValue;
564
565 }
566
567 // -------------------- Future may be different --------------------
568
569 private static final MessageBytesFactory factory=new MessageBytesFactory();
570
571 private static class MessageBytesFactory {
572 protected MessageBytesFactory() {
573 }
574 public MessageBytes newInstance() {
575 return new MessageBytes();
576 }
577 }
578 }
579