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