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    }