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 */
017
018 package org.apache.commons.io.input;
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.nio.ByteBuffer;
023 import java.nio.CharBuffer;
024 import java.nio.charset.CharacterCodingException;
025 import java.nio.charset.Charset;
026 import java.nio.charset.CharsetEncoder;
027 import java.nio.charset.CoderResult;
028 import java.nio.charset.CodingErrorAction;
029
030 /**
031 * {@link InputStream} implementation that can read from String, StringBuffer,
032 * StringBuilder or CharBuffer.
033 * <p>
034 * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
035 *
036 * @since 2.2
037 */
038 public class CharSequenceInputStream extends InputStream {
039
040 private final CharsetEncoder encoder;
041 private final CharBuffer cbuf;
042 private final ByteBuffer bbuf;
043
044 private int mark;
045
046 /**
047 * Constructor.
048 *
049 * @param s the input character sequence
050 * @param charset the character set name to use
051 * @param bufferSize the buffer size to use.
052 */
053 public CharSequenceInputStream(final CharSequence s, final Charset charset, int bufferSize) {
054 super();
055 this.encoder = charset.newEncoder()
056 .onMalformedInput(CodingErrorAction.REPLACE)
057 .onUnmappableCharacter(CodingErrorAction.REPLACE);
058 this.bbuf = ByteBuffer.allocate(bufferSize);
059 this.bbuf.flip();
060 this.cbuf = CharBuffer.wrap(s);
061 this.mark = -1;
062 }
063
064 /**
065 * Constructor, calls {@link #CharSequenceInputStream(CharSequence, Charset, int)}.
066 *
067 * @param s the input character sequence
068 * @param charset the character set name to use
069 * @param bufferSize the buffer size to use.
070 */
071 public CharSequenceInputStream(final CharSequence s, final String charset, int bufferSize) {
072 this(s, Charset.forName(charset), bufferSize);
073 }
074
075 /**
076 * Constructor, calls {@link #CharSequenceInputStream(CharSequence, Charset, int)}
077 * with a buffer size of 2048.
078 *
079 * @param s the input character sequence
080 * @param charset the character set name to use
081 */
082 public CharSequenceInputStream(final CharSequence s, final Charset charset) {
083 this(s, charset, 2048);
084 }
085
086 /**
087 * Constructor, calls {@link #CharSequenceInputStream(CharSequence, String, int)}
088 * with a buffer size of 2048.
089 *
090 * @param s the input character sequence
091 * @param charset the character set name to use
092 */
093 public CharSequenceInputStream(final CharSequence s, final String charset) {
094 this(s, charset, 2048);
095 }
096
097 /**
098 * Fills the byte output buffer from the input char buffer.
099 *
100 * @throws CharacterCodingException
101 * an error encoding data
102 */
103 private void fillBuffer() throws CharacterCodingException {
104 this.bbuf.compact();
105 CoderResult result = this.encoder.encode(this.cbuf, this.bbuf, true);
106 if (result.isError()) {
107 result.throwException();
108 }
109 this.bbuf.flip();
110 }
111
112 @Override
113 public int read(byte[] b, int off, int len) throws IOException {
114 if (b == null) {
115 throw new NullPointerException("Byte array is null");
116 }
117 if (len < 0 || (off + len) > b.length) {
118 throw new IndexOutOfBoundsException("Array Size=" + b.length +
119 ", offset=" + off + ", length=" + len);
120 }
121 if (len == 0) {
122 return 0; // must return 0 for zero length read
123 }
124 if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
125 return -1;
126 }
127 int bytesRead = 0;
128 while (len > 0) {
129 if (this.bbuf.hasRemaining()) {
130 int chunk = Math.min(this.bbuf.remaining(), len);
131 this.bbuf.get(b, off, chunk);
132 off += chunk;
133 len -= chunk;
134 bytesRead += chunk;
135 } else {
136 fillBuffer();
137 if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
138 break;
139 }
140 }
141 }
142 return bytesRead == 0 && !this.cbuf.hasRemaining() ? -1 : bytesRead;
143 }
144
145 @Override
146 public int read() throws IOException {
147 for (;;) {
148 if (this.bbuf.hasRemaining()) {
149 return this.bbuf.get() & 0xFF;
150 } else {
151 fillBuffer();
152 if (!this.bbuf.hasRemaining() && !this.cbuf.hasRemaining()) {
153 return -1;
154 }
155 }
156 }
157 }
158
159 @Override
160 public int read(byte[] b) throws IOException {
161 return read(b, 0, b.length);
162 }
163
164 @Override
165 public long skip(long n) throws IOException {
166 int skipped = 0;
167 while (n > 0 && this.cbuf.hasRemaining()) {
168 this.cbuf.get();
169 n--;
170 skipped++;
171 }
172 return skipped;
173 }
174
175 @Override
176 public int available() throws IOException {
177 return this.cbuf.remaining();
178 }
179
180 @Override
181 public void close() throws IOException {
182 }
183
184 /**
185 * {@inheritDoc}
186 * @param readlimit max read limit (ignored)
187 */
188 @Override
189 public synchronized void mark(@SuppressWarnings("unused") int readlimit) {
190 this.mark = this.cbuf.position();
191 }
192
193 @Override
194 public synchronized void reset() throws IOException {
195 if (this.mark != -1) {
196 this.cbuf.position(this.mark);
197 this.mark = -1;
198 }
199 }
200
201 @Override
202 public boolean markSupported() {
203 return true;
204 }
205
206 }