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
21 /**
22 * Utilities to manipulate char chunks. While String is the easiest way to
23 * manipulate chars ( search, substrings, etc), it is known to not be the most
24 * efficient solution - Strings are designed as immutable and secure objects.
25 *
26 * @author dac@sun.com
27 * @author James Todd [gonzo@sun.com]
28 * @author Costin Manolache
29 * @author Remy Maucherat
30 */
31 public final class CharChunk extends AbstractChunk implements CharSequence {
32
33 private static final long serialVersionUID = 1L;
34
35 /**
36 * Input interface, used when the buffer is empty.
37 */
38 public static interface CharInputChannel {
39
40 /**
41 * Read new characters.
42 *
43 * @return The number of characters read
44 *
45 * @throws IOException If an I/O error occurs during reading
46 */
47 public int realReadChars() throws IOException;
48 }
49
50 /**
51 * When we need more space we'll either grow the buffer ( up to the limit )
52 * or send it to a channel.
53 */
54 public static interface CharOutputChannel {
55
56 /**
57 * Send the bytes ( usually the internal conversion buffer ). Expect 8k
58 * output if the buffer is full.
59 *
60 * @param buf characters that will be written
61 * @param off offset in the characters array
62 * @param len length that will be written
63 * @throws IOException If an I/O occurs while writing the characters
64 */
65 public void realWriteChars(char buf[], int off, int len) throws IOException;
66 }
67
68 // --------------------
69
70 // char[]
71 private char[] buff;
72
73 // transient as serialization is primarily for values via, e.g. JMX
74 private transient CharInputChannel in = null;
75 private transient CharOutputChannel out = null;
76
77
78 /**
79 * Creates a new, uninitialized CharChunk object.
80 */
81 public CharChunk() {
82 }
83
84
85 public CharChunk(int initial) {
86 allocate(initial, -1);
87 }
88
89
90 // --------------------
91
92 @Override
93 public Object clone() throws CloneNotSupportedException {
94 return super.clone();
95 }
96
97
98 // -------------------- Setup --------------------
99
100 public void allocate(int initial, int limit) {
101 if (buff == null || buff.length < initial) {
102 buff = new char[initial];
103 }
104 setLimit(limit);
105 start = 0;
106 end = 0;
107 isSet = true;
108 hasHashCode = false;
109 }
110
111
112 /**
113 * Sets the buffer to the specified subarray of characters.
114 *
115 * @param c the characters
116 * @param off the start offset of the characters
117 * @param len the length of the characters
118 */
119 public void setChars(char[] c, int off, int len) {
120 buff = c;
121 start = off;
122 end = start + len;
123 isSet = true;
124 hasHashCode = false;
125 }
126
127
128 /**
129 * @return the buffer.
130 */
131 public char[] getChars() {
132 return getBuffer();
133 }
134
135
136 /**
137 * @return the buffer.
138 */
139 public char[] getBuffer() {
140 return buff;
141 }
142
143
144 /**
145 * When the buffer is empty, read the data from the input channel.
146 *
147 * @param in The input channel
148 */
149 public void setCharInputChannel(CharInputChannel in) {
150 this.in = in;
151 }
152
153
154 /**
155 * When the buffer is full, write the data to the output channel. Also used
156 * when large amount of data is appended. If not set, the buffer will grow
157 * to the limit.
158 *
159 * @param out The output channel
160 */
161 public void setCharOutputChannel(CharOutputChannel out) {
162 this.out = out;
163 }
164
165
166 // -------------------- Adding data to the buffer --------------------
167
168 public void append(char c) throws IOException {
169 makeSpace(1);
170 int limit = getLimitInternal();
171
172 // couldn't make space
173 if (end >= limit) {
174 flushBuffer();
175 }
176 buff[end++] = c;
177 }
178
179
180 public void append(CharChunk src) throws IOException {
181 append(src.getBuffer(), src.getOffset(), src.getLength());
182 }
183
184
185 /**
186 * Add data to the buffer.
187 *
188 * @param src Char array
189 * @param off Offset
190 * @param len Length
191 * @throws IOException Writing overflow data to the output channel failed
192 */
193 public void append(char src[], int off, int len) throws IOException {
194 // will grow, up to limit
195 makeSpace(len);
196 int limit = getLimitInternal();
197
198 // Optimize on a common case.
199 // If the buffer is empty and the source is going to fill up all the
200 // space in buffer, may as well write it directly to the output,
201 // and avoid an extra copy
202 if (len == limit && end == start && out != null) {
203 out.realWriteChars(src, off, len);
204 return;
205 }
206
207 // if we are below the limit
208 if (len <= limit - end) {
209 System.arraycopy(src, off, buff, end, len);
210 end += len;
211 return;
212 }
213
214 // Need more space than we can afford, need to flush buffer.
215
216 // The buffer is already at (or bigger than) limit.
217
218 // Optimization:
219 // If len-avail < length (i.e. after we fill the buffer with what we
220 // can, the remaining will fit in the buffer) we'll just copy the first
221 // part, flush, then copy the second part - 1 write and still have some
222 // space for more. We'll still have 2 writes, but we write more on the first.
223
224 if (len + end < 2 * limit) {
225 /*
226 * If the request length exceeds the size of the output buffer,
227 * flush the output buffer and then write the data directly. We
228 * can't avoid 2 writes, but we can write more on the second
229 */
230 int avail = limit - end;
231 System.arraycopy(src, off, buff, end, avail);
232 end += avail;
233
234 flushBuffer();
235
236 System.arraycopy(src, off + avail, buff, end, len - avail);
237 end += len - avail;
238
239 } else { // len > buf.length + avail
240 // long write - flush the buffer and write the rest
241 // directly from source
242 flushBuffer();
243
244 out.realWriteChars(src, off, len);
245 }
246 }
247
248
249 /**
250 * Append a string to the buffer.
251 *
252 * @param s The string
253 * @throws IOException Writing overflow data to the output channel failed
254 */
255 public void append(String s) throws IOException {
256 append(s, 0, s.length());
257 }
258
259
260 /**
261 * Append a string to the buffer.
262 *
263 * @param s The string
264 * @param off Offset
265 * @param len Length
266 * @throws IOException Writing overflow data to the output channel failed
267 */
268 public void append(String s, int off, int len) throws IOException {
269 if (s == null) {
270 return;
271 }
272
273 // will grow, up to limit
274 makeSpace(len);
275 int limit = getLimitInternal();
276
277 int sOff = off;
278 int sEnd = off + len;
279 while (sOff < sEnd) {
280 int d = min(limit - end, sEnd - sOff);
281 s.getChars(sOff, sOff + d, buff, end);
282 sOff += d;
283 end += d;
284 if (end >= limit) {
285 flushBuffer();
286 }
287 }
288 }
289
290
291 // -------------------- Removing data from the buffer --------------------
292
293 /*
294 * @deprecated Use {@link #subtract()}.
295 * This method will be removed in Tomcat 10
296 */
297 @Deprecated
298 public int substract() throws IOException {
299 return subtract();
300 }
301
302 public int subtract() throws IOException {
303 if (checkEof()) {
304 return -1;
305 }
306 return buff[start++];
307 }
308
309
310 /*
311 * @deprecated Use {@link #subtract(char[],int,int)}.
312 * This method will be removed in Tomcat 10
313 */
314 @Deprecated
315 public int substract(char dest[], int off, int len) throws IOException {
316 return subtract(dest, off, len);
317 }
318
319 public int subtract(char dest[], int off, int len) throws IOException {
320 if (checkEof()) {
321 return -1;
322 }
323 int n = len;
324 if (len > getLength()) {
325 n = getLength();
326 }
327 System.arraycopy(buff, start, dest, off, n);
328 start += n;
329 return n;
330 }
331
332
333 private boolean checkEof() throws IOException {
334 if ((end - start) == 0) {
335 if (in == null) {
336 return true;
337 }
338 int n = in.realReadChars();
339 if (n < 0) {
340 return true;
341 }
342 }
343 return false;
344 }
345
346
347 /**
348 * Send the buffer to the sink. Called by append() when the limit is
349 * reached. You can also call it explicitly to force the data to be written.
350 *
351 * @throws IOException Writing overflow data to the output channel failed
352 */
353 public void flushBuffer() throws IOException {
354 // assert out!=null
355 if (out == null) {
356 throw new IOException(sm.getString("chunk.overflow",
357 Integer.valueOf(getLimit()), Integer.valueOf(buff.length)));
358 }
359 out.realWriteChars(buff, start, end - start);
360 end = start;
361 }
362
363
364 /**
365 * Make space for len chars. If len is small, allocate a reserve space too.
366 * Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}.
367 *
368 * @param count The size
369 */
370 public void makeSpace(int count) {
371 char[] tmp = null;
372
373 int limit = getLimitInternal();
374
375 long newSize;
376 long desiredSize = end + count;
377
378 // Can't grow above the limit
379 if (desiredSize > limit) {
380 desiredSize = limit;
381 }
382
383 if (buff == null) {
384 if (desiredSize < 256) {
385 desiredSize = 256; // take a minimum
386 }
387 buff = new char[(int) desiredSize];
388 }
389
390 // limit < buf.length (the buffer is already big)
391 // or we already have space XXX
392 if (desiredSize <= buff.length) {
393 return;
394 }
395 // grow in larger chunks
396 if (desiredSize < 2L * buff.length) {
397 newSize = buff.length * 2L;
398 } else {
399 newSize = buff.length * 2L + count;
400 }
401
402 if (newSize > limit) {
403 newSize = limit;
404 }
405 tmp = new char[(int) newSize];
406
407 // Some calling code assumes buffer will not be compacted
408 System.arraycopy(buff, 0, tmp, 0, end);
409 buff = tmp;
410 tmp = null;
411 }
412
413
414 // -------------------- Conversion and getters --------------------
415
416 @Override
417 public String toString() {
418 if (isNull()) {
419 return null;
420 } else if (end - start == 0) {
421 return "";
422 }
423 return StringCache.toString(this);
424 }
425
426
427 public String toStringInternal() {
428 return new String(buff, start, end - start);
429 }
430
431
432 // -------------------- equals --------------------
433
434 @Override
435 public boolean equals(Object obj) {
436 if (obj instanceof CharChunk) {
437 return equals((CharChunk) obj);
438 }
439 return false;
440 }
441
442
443 /**
444 * Compares the message bytes to the specified String object.
445 *
446 * @param s the String to compare
447 * @return <code>true</code> if the comparison succeeded, <code>false</code>
448 * otherwise
449 */
450 public boolean equals(String s) {
451 char[] c = buff;
452 int len = end - start;
453 if (c == null || len != s.length()) {
454 return false;
455 }
456 int off = start;
457 for (int i = 0; i < len; i++) {
458 if (c[off++] != s.charAt(i)) {
459 return false;
460 }
461 }
462 return true;
463 }
464
465
466 /**
467 * Compares the message bytes to the specified String object.
468 *
469 * @param s the String to compare
470 * @return <code>true</code> if the comparison succeeded, <code>false</code>
471 * otherwise
472 */
473 public boolean equalsIgnoreCase(String s) {
474 char[] c = buff;
475 int len = end - start;
476 if (c == null || len != s.length()) {
477 return false;
478 }
479 int off = start;
480 for (int i = 0; i < len; i++) {
481 if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
482 return false;
483 }
484 }
485 return true;
486 }
487
488
489 public boolean equals(CharChunk cc) {
490 return equals(cc.getChars(), cc.getOffset(), cc.getLength());
491 }
492
493
494 public boolean equals(char b2[], int off2, int len2) {
495 char b1[] = buff;
496 if (b1 == null && b2 == null) {
497 return true;
498 }
499
500 int len = end - start;
501 if (len != len2 || b1 == null || b2 == null) {
502 return false;
503 }
504
505 int off1 = start;
506
507 while (len-- > 0) {
508 if (b1[off1++] != b2[off2++]) {
509 return false;
510 }
511 }
512 return true;
513 }
514
515
516 /**
517 * @return <code>true</code> if the message bytes starts with the specified
518 * string.
519 * @param s The string
520 */
521 public boolean startsWith(String s) {
522 char[] c = buff;
523 int len = s.length();
524 if (c == null || len > end - start) {
525 return false;
526 }
527 int off = start;
528 for (int i = 0; i < len; i++) {
529 if (c[off++] != s.charAt(i)) {
530 return false;
531 }
532 }
533 return true;
534 }
535
536
537 /**
538 * Returns true if the buffer starts with the specified string.
539 *
540 * @param s the string
541 * @param pos The position
542 *
543 * @return <code>true</code> if the start matches
544 */
545 public boolean startsWithIgnoreCase(String s, int pos) {
546 char[] c = buff;
547 int len = s.length();
548 if (c == null || len + pos > end - start) {
549 return false;
550 }
551 int off = start + pos;
552 for (int i = 0; i < len; i++) {
553 if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
554 return false;
555 }
556 }
557 return true;
558 }
559
560
561 /**
562 * @return <code>true</code> if the message bytes end with the specified
563 * string.
564 * @param s The string
565 */
566 public boolean endsWith(String s) {
567 char[] c = buff;
568 int len = s.length();
569 if (c == null || len > end - start) {
570 return false;
571 }
572 int off = end - len;
573 for (int i = 0; i < len; i++) {
574 if (c[off++] != s.charAt(i)) {
575 return false;
576 }
577 }
578 return true;
579 }
580
581
582 @Override
583 protected int getBufferElement(int index) {
584 return buff[index];
585 }
586
587
588 public int indexOf(char c) {
589 return indexOf(c, start);
590 }
591
592
593 /**
594 * Returns the first instance of the given character in this CharChunk
595 * starting at the specified char. If the character is not found, -1 is
596 * returned. <br>
597 *
598 * @param c The character
599 * @param starting The start position
600 * @return The position of the first instance of the character or -1 if the
601 * character is not found.
602 */
603 public int indexOf(char c, int starting) {
604 int ret = indexOf(buff, start + starting, end, c);
605 return (ret >= start) ? ret - start : -1;
606 }
607
608
609 /**
610 * Returns the first instance of the given character in the given char array
611 * between the specified start and end. <br>
612 *
613 * @param chars The array to search
614 * @param start The point to start searching from in the array
615 * @param end The point to stop searching in the array
616 * @param s The character to search for
617 * @return The position of the first instance of the character or -1 if the
618 * character is not found.
619 */
620 public static int indexOf(char chars[], int start, int end, char s) {
621 int offset = start;
622
623 while (offset < end) {
624 char c = chars[offset];
625 if (c == s) {
626 return offset;
627 }
628 offset++;
629 }
630 return -1;
631 }
632
633
634 // -------------------- utils
635 private int min(int a, int b) {
636 if (a < b) {
637 return a;
638 }
639 return b;
640 }
641
642
643 // Char sequence impl
644
645 @Override
646 public char charAt(int index) {
647 return buff[index + start];
648 }
649
650
651 @Override
652 public CharSequence subSequence(int start, int end) {
653 try {
654 CharChunk result = (CharChunk) this.clone();
655 result.setOffset(this.start + start);
656 result.setEnd(this.start + end);
657 return result;
658 } catch (CloneNotSupportedException e) {
659 // Cannot happen
660 return null;
661 }
662 }
663
664
665 @Override
666 public int length() {
667 return end - start;
668 }
669
670 /**
671 * NO-OP.
672 *
673 * @param optimizedWrite Ignored
674 *
675 * @deprecated Unused code. This is now a NO-OP and will be removed without
676 * replacement in Tomcat 10.
677 */
678 @Deprecated
679 public void setOptimizedWrite(boolean optimizedWrite) {
680 }
681 }
682