View Javadoc
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 }