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.juneau.commons.io;
18
19 import static org.apache.juneau.commons.utils.AssertionUtils.*;
20 import static org.apache.juneau.commons.utils.Utils.*;
21
22 import java.io.*;
23
24 /**
25 * A {@link Reader} implementation that reads from any {@link CharSequence} (String, StringBuilder, StringBuffer, etc.).
26 *
27 * <p>
28 * This class extends {@link BufferedReader} and provides efficient reading from any {@link CharSequence}
29 * implementation. Unlike {@link StringReader}, which only works with {@link String}, this class can
30 * read from {@link StringBuilder}, {@link StringBuffer}, or any other {@link CharSequence} implementation.
31 *
32 * <h5 class='section'>Features:</h5>
33 * <ul class='spaced-list'>
34 * <li>Generic CharSequence support - works with String, StringBuilder, StringBuffer, and other CharSequence types
35 * <li>Efficient reading - optimized for different CharSequence types
36 * <li>No-op close - close() method does nothing (CharSequence is in-memory)
37 * <li>No mark support - mark/reset operations are not supported
38 * </ul>
39 *
40 * <h5 class='section'>Use Cases:</h5>
41 * <ul class='spaced-list'>
42 * <li>Reading from StringBuilder or StringBuffer instances
43 * <li>Converting CharSequence data to Reader for APIs that require Reader
44 * <li>Processing character data from various CharSequence sources
45 * <li>Testing scenarios where you need a Reader from in-memory data
46 * </ul>
47 *
48 * <h5 class='section'>Usage:</h5>
49 * <p class='bjava'>
50 * <jc>// Read from String</jc>
51 * CharSequenceReader <jv>reader1</jv> = <jk>new</jk> CharSequenceReader(<js>"Hello World"</js>);
52 *
53 * <jc>// Read from StringBuilder</jc>
54 * StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"Dynamic content"</js>);
55 * CharSequenceReader <jv>reader2</jv> = <jk>new</jk> CharSequenceReader(<jv>sb</jv>);
56 *
57 * <jc>// Read from StringBuffer</jc>
58 * StringBuffer <jv>sbuf</jv> = <jk>new</jk> StringBuffer(<js>"Buffer content"</js>);
59 * CharSequenceReader <jv>reader3</jv> = <jk>new</jk> CharSequenceReader(<jv>sbuf</jv>);
60 *
61 * <jc>// Use with APIs that require Reader</jc>
62 * <jk>try</jk> (CharSequenceReader <jv>reader</jv> = <jk>new</jk> CharSequenceReader(<js>"data"</js>)) {
63 * <jc>// Process reader</jc>
64 * }
65 * </p>
66 *
67 * <h5 class='section'>Performance:</h5>
68 * <p>
69 * This class optimizes reading based on the CharSequence type:
70 * <ul class='spaced-list'>
71 * <li>String - uses efficient {@link String#getChars(int, int, char[], int)} method
72 * <li>StringBuffer - uses efficient {@link StringBuffer#getChars(int, int, char[], int)} method
73 * <li>StringBuilder - uses efficient {@link StringBuilder#getChars(int, int, char[], int)} method
74 * <li>Other CharSequence - falls back to character-by-character reading via {@link CharSequence#charAt(int)}
75 * </ul>
76 *
77 * <h5 class='section'>Thread Safety:</h5>
78 * <p>
79 * This class is not thread-safe. If the underlying CharSequence is modified by another thread
80 * while reading, the behavior is undefined. For thread-safe reading, ensure the CharSequence
81 * is not modified during reading, or use external synchronization.
82 *
83 * <h5 class='section'>See Also:</h5><ul>
84 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a>
85 * </ul>
86 */
87 public class CharSequenceReader extends BufferedReader {
88
89 private final CharSequence cs;
90 private String s;
91 private StringBuffer sb;
92 private StringBuilder sb2;
93 private int length;
94 private int next;
95
96 /**
97 * Constructor.
98 *
99 * <p>
100 * Creates a new CharSequenceReader that reads from the specified CharSequence. If the
101 * CharSequence is <jk>null</jk>, it is treated as an empty string.
102 *
103 * <h5 class='section'>Example:</h5>
104 * <p class='bjava'>
105 * <jc>// From String</jc>
106 * CharSequenceReader <jv>reader1</jv> = <jk>new</jk> CharSequenceReader(<js>"Hello"</js>);
107 *
108 * <jc>// From StringBuilder</jc>
109 * StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"World"</js>);
110 * CharSequenceReader <jv>reader2</jv> = <jk>new</jk> CharSequenceReader(<jv>sb</jv>);
111 *
112 * <jc>// Null is treated as empty string</jc>
113 * CharSequenceReader <jv>reader3</jv> = <jk>new</jk> CharSequenceReader(<jk>null</jk>);
114 * <jk>int</jk> <jv>ch</jv> = <jv>reader3</jv>.read(); <jc>// Returns -1 (EOF)</jc>
115 * </p>
116 *
117 * @param cs The CharSequence to read from. Can be <jk>null</jk> (treated as empty string).
118 */
119 public CharSequenceReader(CharSequence cs) {
120 super(new StringReader(""), 1); // Does not actually use a reader.
121 if (cs == null)
122 cs = "";
123 this.cs = cs;
124 if (cs instanceof String s2)
125 s = s2;
126 else if (cs instanceof StringBuffer sb3)
127 sb = sb3;
128 else if (cs instanceof StringBuilder sb4)
129 sb2 = sb4;
130 this.length = cs.length();
131 }
132
133 @Override /* Overridden from Reader */
134 public void close() {
135 // no-op
136 }
137
138 @Override /* Overridden from Reader */
139 public boolean markSupported() {
140 return false;
141 }
142
143 @Override /* Overridden from Reader */
144 public int read() {
145 if (next >= length)
146 return -1;
147 return cs.charAt(next++);
148 }
149
150 @Override /* Overridden from Reader */
151 public int read(char[] cbuf, int off, int len) {
152 assertArgNotNull("cbuf", cbuf);
153 if (next >= length)
154 return -1;
155 int n = Math.min(length - next, len);
156 if (nn(s))
157 s.getChars(next, next + n, cbuf, off);
158 else if (nn(sb))
159 sb.getChars(next, next + n, cbuf, off);
160 else if (nn(sb2))
161 sb2.getChars(next, next + n, cbuf, off);
162 else {
163 for (var i = 0; i < n; i++)
164 cbuf[off + i] = cs.charAt(next + i);
165 }
166 next += n;
167 return n;
168 }
169
170 @Override /* Overridden from Reader */
171 public long skip(long ns) {
172 if (next >= length)
173 return 0;
174 long n = Math.min(length - next, ns);
175 n = Math.max(-next, n);
176 next += n;
177 return n;
178 }
179
180 @Override /* Overridden from Object */
181 public String toString() {
182 return cs.toString();
183 }
184 }