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.codec.binary;
019    
020    import java.io.FilterInputStream;
021    import java.io.IOException;
022    import java.io.InputStream;
023    
024    /**
025     * Abstract superclass for Base-N input streams.
026     * 
027     * @since 1.5
028     */
029    public class BaseNCodecInputStream extends FilterInputStream {
030    
031        private final boolean doEncode;
032    
033        private final BaseNCodec baseNCodec;
034    
035        private final byte[] singleByte = new byte[1];
036    
037        protected BaseNCodecInputStream(InputStream in, BaseNCodec baseNCodec, boolean doEncode) {
038            super(in);
039            this.doEncode = doEncode;
040            this.baseNCodec = baseNCodec;
041        }
042    
043        /**
044         * Reads one <code>byte</code> from this input stream.
045         * 
046         * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
047         * @throws IOException
048         *             if an I/O error occurs.
049         */
050        public int read() throws IOException {
051            int r = read(singleByte, 0, 1);
052            while (r == 0) {
053                r = read(singleByte, 0, 1);
054            }
055            if (r > 0) {
056                return singleByte[0] < 0 ? 256 + singleByte[0] : singleByte[0];
057            }
058            return -1;
059        }
060    
061        /**
062         * Attempts to read <code>len</code> bytes into the specified <code>b</code> array starting at <code>offset</code>
063         * from this InputStream.
064         * 
065         * @param b
066         *            destination byte array
067         * @param offset
068         *            where to start writing the bytes
069         * @param len
070         *            maximum number of bytes to read
071         * 
072         * @return number of bytes read
073         * @throws IOException
074         *             if an I/O error occurs.
075         * @throws NullPointerException
076         *             if the byte array parameter is null
077         * @throws IndexOutOfBoundsException
078         *             if offset, len or buffer size are invalid
079         */
080        public int read(byte b[], int offset, int len) throws IOException {
081            if (b == null) {
082                throw new NullPointerException();
083            } else if (offset < 0 || len < 0) {
084                throw new IndexOutOfBoundsException();
085            } else if (offset > b.length || offset + len > b.length) {
086                throw new IndexOutOfBoundsException();
087            } else if (len == 0) {
088                return 0;
089            } else {
090                int readLen = 0;
091                /*
092                 Rationale for while-loop on (readLen == 0):
093                 -----
094                 Base32.readResults() usually returns > 0 or EOF (-1).  In the
095                 rare case where it returns 0, we just keep trying.
096    
097                 This is essentially an undocumented contract for InputStream
098                 implementors that want their code to work properly with
099                 java.io.InputStreamReader, since the latter hates it when
100                 InputStream.read(byte[]) returns a zero.  Unfortunately our
101                 readResults() call must return 0 if a large amount of the data
102                 being decoded was non-base32, so this while-loop enables proper
103                 interop with InputStreamReader for that scenario.
104                 -----
105                 This is a fix for CODEC-101
106                */
107                while (readLen == 0) {
108                    if (!baseNCodec.hasData()) {
109                        byte[] buf = new byte[doEncode ? 4096 : 8192];
110                        int c = in.read(buf);
111                        if (doEncode) {
112                            baseNCodec.encode(buf, 0, c);
113                        } else {
114                            baseNCodec.decode(buf, 0, c);
115                        }
116                    }
117                    readLen = baseNCodec.readResults(b, offset, len);
118                }
119                return readLen;
120            }
121        }
122        /**
123         * {@inheritDoc}
124         * 
125         * @return false
126         */
127        public boolean markSupported() {
128            return false; // not an easy job to support marks
129        }
130    
131    }