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}