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}