001    /*
002     * Apache License
003     * Version 2.0, January 2004
004     * http://www.apache.org/licenses/
005     *
006     * Copyright 2008 by chenillekit.org
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     */
014    package org.chenillekit.core.streams;
015    
016    import java.io.IOException;
017    import java.io.InputStream;
018    import java.io.Reader;
019    
020    /**
021     * Adapts a <code>Reader</code> as an <code>InputStream</code>.
022     * Adapted from <CODE>StringInputStream</CODE>.
023     * <p/>
024     * stolen from org.apache.tools.ant.util.ReaderInputStream
025     *
026     */
027    public class ReaderInputStream extends InputStream {
028    
029        /** Source Reader */
030        private Reader in;
031    
032        private String encoding = System.getProperty("file.encoding");
033    
034        private byte[] slack;
035    
036        private int begin;
037    
038        /**
039         * Construct a <CODE>ReaderInputStream</CODE>
040         * for the specified <CODE>Reader</CODE>.
041         *
042         * @param reader   <CODE>Reader</CODE>.  Must not be <code>null</code>.
043         */
044        public ReaderInputStream(Reader reader) {
045            in = reader;
046        }
047    
048        /**
049         * Construct a <CODE>ReaderInputStream</CODE>
050         * for the specified <CODE>Reader</CODE>,
051         * with the specified encoding.
052         *
053         * @param reader     non-null <CODE>Reader</CODE>.
054         * @param encoding   non-null <CODE>String</CODE> encoding.
055         */
056        public ReaderInputStream(Reader reader, String encoding) {
057            this(reader);
058            if (encoding == null) {
059                throw new IllegalArgumentException("encoding must not be null");
060            } else {
061                this.encoding = encoding;
062            }
063        }
064    
065        /**
066         * Reads from the <CODE>Reader</CODE>, returning the same value.
067         *
068         * @return the value of the next character in the <CODE>Reader</CODE>.
069         *
070         * @exception java.io.IOException if the original <code>Reader</code> fails to be read
071         */
072        public synchronized int read() throws IOException {
073            if (in == null) {
074                throw new IOException("Stream Closed");
075            }
076    
077            byte result;
078            if (slack != null && begin < slack.length) {
079                result = slack[begin];
080                if (++begin == slack.length) {
081                    slack = null;
082                }
083            } else {
084                byte[] buf = new byte[1];
085                if (read(buf, 0, 1) <= 0) {
086                    result = -1;
087                }
088                result = buf[0];
089            }
090    
091            if (result < -1) {
092                result+= 256;
093            }
094    
095            return result;
096        }
097    
098        /**
099         * Reads from the <code>Reader</code> into a byte array
100         *
101         * @param b  the byte array to read into
102         * @param off the offset in the byte array
103         * @param len the length in the byte array to fill
104         * @return the actual number read into the byte array, -1 at
105         *         the end of the stream
106         * @exception java.io.IOException if an error occurs
107         */
108        public synchronized int read(byte[] b, int off, int len)
109            throws IOException {
110            if (in == null) {
111                throw new IOException("Stream Closed");
112            }
113    
114            while (slack == null) {
115                char[] buf = new char[len]; // might read too much
116                int n = in.read(buf);
117                if (n == -1) {
118                    return -1;
119                }
120                if (n > 0) {
121                    slack = new String(buf, 0, n).getBytes(encoding);
122                    begin = 0;
123                }
124            }
125    
126            if (len > slack.length - begin) {
127                len = slack.length - begin;
128            }
129    
130            System.arraycopy(slack, begin, b, off, len);
131    
132            if ((begin += len) >= slack.length) {
133                slack = null;
134            }
135    
136            return len;
137        }
138    
139        /**
140         * Marks the read limit of the StringReader.
141         *
142         * @param limit the maximum limit of bytes that can be read before the
143         *              mark position becomes invalid
144         */
145        public synchronized void mark(final int limit) {
146            try {
147                in.mark(limit);
148            } catch (IOException ioe) {
149                throw new RuntimeException(ioe.getMessage());
150            }
151        }
152    
153    
154        /**
155         * @return   the current number of bytes ready for reading
156         * @exception java.io.IOException if an error occurs
157         */
158        public synchronized int available() throws IOException {
159            if (in == null) {
160                throw new IOException("Stream Closed");
161            }
162            if (slack != null) {
163                return slack.length - begin;
164            }
165            if (in.ready()) {
166                return 1;
167            } else {
168                return 0;
169            }
170        }
171    
172        /**
173         * @return false - mark is not supported
174         */
175        public boolean markSupported () {
176            return false;   // would be imprecise
177        }
178    
179        /**
180         * Resets the StringReader.
181         *
182         * @exception java.io.IOException if the StringReader fails to be reset
183         */
184        public synchronized void reset() throws IOException {
185            if (in == null) {
186                throw new IOException("Stream Closed");
187            }
188            slack = null;
189            in.reset();
190        }
191    
192        /**
193         * Closes the Stringreader.
194         *
195         * @exception java.io.IOException if the original StringReader fails to be closed
196         */
197        public synchronized void close() throws IOException {
198            in.close();
199            slack = null;
200            in = null;
201        }
202    }