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.Shorts.*;
020
021import java.io.*;
022
023/**
024 * A {@link Reader} implementation that reads from any {@link CharSequence} (String, StringBuilder, StringBuffer, etc.).
025 *
026 * <p>
027 * This class extends {@link BufferedReader} and provides efficient reading from any {@link CharSequence}
028 * implementation. Unlike {@link StringReader}, which only works with {@link String}, this class can
029 * read from {@link StringBuilder}, {@link StringBuffer}, or any other {@link CharSequence} implementation.
030 *
031 * <h5 class='section'>Features:</h5>
032 * <ul class='spaced-list'>
033 *    <li>Generic CharSequence support - works with String, StringBuilder, StringBuffer, and other CharSequence types
034 *    <li>Efficient reading - optimized for different CharSequence types
035 *    <li>No-op close - close() method does nothing (CharSequence is in-memory)
036 *    <li>No mark support - mark/reset operations are not supported
037 * </ul>
038 *
039 * <h5 class='section'>Use Cases:</h5>
040 * <ul class='spaced-list'>
041 *    <li>Reading from StringBuilder or StringBuffer instances
042 *    <li>Converting CharSequence data to Reader for APIs that require Reader
043 *    <li>Processing character data from various CharSequence sources
044 *    <li>Testing scenarios where you need a Reader from in-memory data
045 * </ul>
046 *
047 * <h5 class='section'>Usage:</h5>
048 * <p class='bjava'>
049 *    <jc>// Read from String</jc>
050 *    CharSequenceReader <jv>reader1</jv> = <jk>new</jk> CharSequenceReader(<js>"Hello World"</js>);
051 *
052 *    <jc>// Read from StringBuilder</jc>
053 *    StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"Dynamic content"</js>);
054 *    CharSequenceReader <jv>reader2</jv> = <jk>new</jk> CharSequenceReader(<jv>sb</jv>);
055 *
056 *    <jc>// Read from StringBuffer</jc>
057 *    StringBuffer <jv>sbuf</jv> = <jk>new</jk> StringBuffer(<js>"Buffer content"</js>);
058 *    CharSequenceReader <jv>reader3</jv> = <jk>new</jk> CharSequenceReader(<jv>sbuf</jv>);
059 *
060 *    <jc>// Use with APIs that require Reader</jc>
061 *    <jk>try</jk> (CharSequenceReader <jv>reader</jv> = <jk>new</jk> CharSequenceReader(<js>"data"</js>)) {
062    *       <jc>// Process reader</jc>
063    *    }
064 * </p>
065 *
066 * <h5 class='section'>Performance:</h5>
067 * <p>
068 * This class optimizes reading based on the CharSequence type:
069 * <ul class='spaced-list'>
070 *    <li>String - uses efficient {@link String#getChars(int, int, char[], int)} method
071 *    <li>StringBuffer - uses efficient {@link StringBuffer#getChars(int, int, char[], int)} method
072 *    <li>StringBuilder - uses efficient {@link StringBuilder#getChars(int, int, char[], int)} method
073 *    <li>Other CharSequence - falls back to character-by-character reading via {@link CharSequence#charAt(int)}
074 * </ul>
075 *
076 * <h5 class='section'>Thread Safety:</h5>
077 * <p>
078 * This class is not thread-safe. If the underlying CharSequence is modified by another thread
079 * while reading, the behavior is undefined. For thread-safe reading, ensure the CharSequence
080 * is not modified during reading, or use external synchronization.
081 *
082 * <h5 class='section'>See Also:</h5><ul>
083 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsIO">I/O Package</a>
084 * </ul>
085 */
086@SuppressWarnings({
087   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
088})
089public class CharSequenceReader extends BufferedReader {
090
091   private final CharSequence cs;
092   private final String s;
093   private final StringBuilder sb;
094   private final int length;
095   private int next;
096
097   /**
098    * Constructor.
099    *
100    * <p>
101    * Creates a new CharSequenceReader that reads from the specified CharSequence. If the
102    * CharSequence is <jk>null</jk>, it is treated as an empty string.
103    *
104    * <h5 class='section'>Example:</h5>
105    * <p class='bjava'>
106    *    <jc>// From String</jc>
107    *    CharSequenceReader <jv>reader1</jv> = <jk>new</jk> CharSequenceReader(<js>"Hello"</js>);
108    *
109    *    <jc>// From StringBuilder</jc>
110    *    StringBuilder <jv>sb</jv> = <jk>new</jk> StringBuilder(<js>"World"</js>);
111    *    CharSequenceReader <jv>reader2</jv> = <jk>new</jk> CharSequenceReader(<jv>sb</jv>);
112    *
113    *    <jc>// Null is treated as empty string</jc>
114    *    CharSequenceReader <jv>reader3</jv> = <jk>new</jk> CharSequenceReader(<jk>null</jk>);
115    *    <jk>int</jk> <jv>ch</jv> = <jv>reader3</jv>.read();  <jc>// Returns -1 (EOF)</jc>
116    * </p>
117    *
118    * @param cs The CharSequence to read from. Can be <jk>null</jk> (treated as empty string).
119    */
120   public CharSequenceReader(CharSequence cs) {
121      super(new StringReader(""), 1);   // Does not actually use a reader.
122      if (cs == null)
123         cs = "";
124      this.cs = cs;
125      this.s = cs instanceof String cs2 ? cs2 : null;
126      this.sb = cs instanceof StringBuilder cs2 ? cs2 : null;
127      this.length = cs.length();
128   }
129
130   @Override /* Overridden from Reader */
131   public void close() {
132      // no-op
133   }
134
135   @Override /* Overridden from Reader */
136   public boolean markSupported() {
137      return false;
138   }
139
140   @Override /* Overridden from Reader */
141   public int read() {
142      if (next >= length)
143         return -1;
144      return cs.charAt(next++);
145   }
146
147   @Override /* Overridden from Reader */
148   public int read(char[] cbuf, int off, int len) {
149      reqnn("cbuf", cbuf);
150      if (next >= length)
151         return -1;
152      int n = Math.min(length - next, len);
153      if (nn(s))
154         s.getChars(next, next + n, cbuf, off);
155      else if (nn(sb))
156         sb.getChars(next, next + n, cbuf, off);
157      else {
158         for (var i = 0; i < n; i++)
159            cbuf[off + i] = cs.charAt(next + i);
160      }
161      next += n;
162      return n;
163   }
164
165   @Override /* Overridden from Reader */
166   public long skip(long ns) {
167      if (next >= length)
168         return 0;
169      long n = Math.min((long) length - next, ns);
170      n = Math.max(-next, n);
171      next += (int) n;  // Safe narrowing: n is bounded to [-next, length-next], so next+n stays within int range [0, length].
172      return n;
173   }
174
175   @Override /* Overridden from Object */
176   public String toString() {
177      return cs.toString();
178   }
179}