001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.io;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.io.*;
023
024/**
025 * A {@link Reader} implementation that reads from any {@link CharSequence} (String, StringBuilder, StringBuffer, etc.).
026 *
027 * <p>
028 * This class extends {@link BufferedReader} and provides efficient reading from any {@link CharSequence}
029 * implementation. Unlike {@link StringReader}, which only works with {@link String}, this class can
030 * read from {@link StringBuilder}, {@link StringBuffer}, or any other {@link CharSequence} implementation.
031 *
032 * <h5 class='section'>Features:</h5>
033 * <ul class='spaced-list'>
034 *    <li>Generic CharSequence support - works with String, StringBuilder, StringBuffer, and other CharSequence types
035 *    <li>Efficient reading - optimized for different CharSequence types
036 *    <li>No-op close - close() method does nothing (CharSequence is in-memory)
037 *    <li>No mark support - mark/reset operations are not supported
038 * </ul>
039 *
040 * <h5 class='section'>Use Cases:</h5>
041 * <ul class='spaced-list'>
042 *    <li>Reading from StringBuilder or StringBuffer instances
043 *    <li>Converting CharSequence data to Reader for APIs that require Reader
044 *    <li>Processing character data from various CharSequence sources
045 *    <li>Testing scenarios where you need a Reader from in-memory data
046 * </ul>
047 *
048 * <h5 class='section'>Usage:</h5>
049 * <p class='bjava'>
050 *    <jc>// Read from String</jc>
051 *    CharSequenceReader <jv>reader1</jv> = <jk>new</jk> CharSequenceReader(<js>"Hello World"</js>);
052 *
053 *    <jc>// Read from StringBuilder</jc>
054 *    StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"Dynamic content"</js>);
055 *    CharSequenceReader <jv>reader2</jv> = <jk>new</jk> CharSequenceReader(<jv>sb</jv>);
056 *
057 *    <jc>// Read from StringBuffer</jc>
058 *    StringBuffer <jv>sbuf</jv> = <jk>new</jk> StringBuffer(<js>"Buffer content"</js>);
059 *    CharSequenceReader <jv>reader3</jv> = <jk>new</jk> CharSequenceReader(<jv>sbuf</jv>);
060 *
061 *    <jc>// Use with APIs that require Reader</jc>
062 *    <jk>try</jk> (CharSequenceReader <jv>reader</jv> = <jk>new</jk> CharSequenceReader(<js>"data"</js>)) {
063    *       <jc>// Process reader</jc>
064    *    }
065 * </p>
066 *
067 * <h5 class='section'>Performance:</h5>
068 * <p>
069 * This class optimizes reading based on the CharSequence type:
070 * <ul class='spaced-list'>
071 *    <li>String - uses efficient {@link String#getChars(int, int, char[], int)} method
072 *    <li>StringBuffer - uses efficient {@link StringBuffer#getChars(int, int, char[], int)} method
073 *    <li>StringBuilder - uses efficient {@link StringBuilder#getChars(int, int, char[], int)} method
074 *    <li>Other CharSequence - falls back to character-by-character reading via {@link CharSequence#charAt(int)}
075 * </ul>
076 *
077 * <h5 class='section'>Thread Safety:</h5>
078 * <p>
079 * This class is not thread-safe. If the underlying CharSequence is modified by another thread
080 * while reading, the behavior is undefined. For thread-safe reading, ensure the CharSequence
081 * is not modified during reading, or use external synchronization.
082 *
083 * <h5 class='section'>See Also:</h5><ul>
084 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a>
085 * </ul>
086 */
087public class CharSequenceReader extends BufferedReader {
088
089   private final CharSequence cs;
090   private String s;
091   private StringBuffer sb;
092   private StringBuilder sb2;
093   private int length;
094   private int next;
095
096   /**
097    * Constructor.
098    *
099    * <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}