    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 static org.apache.commons.codec.binary.BaseNCodec.EOF;
    021
    022    import java.io.FilterInputStream;
    023    import java.io.IOException;
    024    import java.io.InputStream;
    025
    026    import org.apache.commons.codec.binary.BaseNCodec.Context;
    027
    028    /**
    029     * Abstract superclass for Base-N input streams.
    030     *
    031     * @since 1.5
    032     * @version $Id: BaseNCodecInputStream.java 1429868 2013-01-07 16:08:05Z ggregory $
    033     */
    034    public class BaseNCodecInputStream extends FilterInputStream {
    035
    036        private final BaseNCodec baseNCodec;
    037
    038        private final boolean doEncode;
    039
    040        private final byte[] singleByte = new byte[1];
    041
    042        private final Context context = new Context();
    043
    044        protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) {
    045            super(in);
    046            this.doEncode = doEncode;
    047            this.baseNCodec = baseNCodec;
    048        }
    049
    050        /**
    051         * {@inheritDoc}
    052         *
    053         * @return <code>0</code> if the {@link InputStream} has reached <code>EOF</code>,
    054         * <code>1</code> otherwise
    055         * @since 1.7
    056         */
    057        @Override
    058        public int available() throws IOException {
    059            // Note: the logic is similar to the InflaterInputStream:
    060            //       as long as we have not reached EOF, indicate that there is more
    061            //       data available. As we do not know for sure how much data is left,
    062            //       just return 1 as a safe guess.
    063
    064            return context.eof ? 0 : 1;
    065        }
    066
    067        /**
    068         * Marks the current position in this input stream.
    069         * <p>The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.</p>
    070         *
    071         * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
    072         * @since 1.7
    073         */
    074        @Override
    075        public synchronized void mark(final int readLimit) {
    076        }
    077
    078        /**
    079         * {@inheritDoc}
    080         *
    081         * @return always returns <code>false</code>
    082         */
    083        @Override
    084        public boolean markSupported() {
    085            return false; // not an easy job to support marks
    086        }
    087
    088        /**
    089         * Reads one <code>byte</code> from this input stream.
    090         *
    091         * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
    092         * @throws IOException
    093         *             if an I/O error occurs.
    094         */
    095        @Override
    096        public int read() throws IOException {
    097            int r = read(singleByte, 0, 1);
    098            while (r == 0) {
    099                r = read(singleByte, 0, 1);
    100            }
    101            if (r > 0) {
    102                final byte b = singleByte[0];
    103                return b < 0 ? 256 + b : b;
    104            }
    105            return EOF;
    106        }
    107
    108        /**
    109         * Attempts to read <code>len</code> bytes into the specified <code>b</code> array starting at <code>offset</code>
    110         * from this InputStream.
    111         *
    112         * @param b
    113         *            destination byte array
    114         * @param offset
    115         *            where to start writing the bytes
    116         * @param len
    117         *            maximum number of bytes to read
    118         *
    119         * @return number of bytes read
    120         * @throws IOException
    121         *             if an I/O error occurs.
    122         * @throws NullPointerException
    123         *             if the byte array parameter is null
    124         * @throws IndexOutOfBoundsException
    125         *             if offset, len or buffer size are invalid
    126         */
    127        @Override
    128        public int read(final byte b[], final int offset, final int len) throws IOException {
    129            if (b == null) {
    130                throw new NullPointerException();
    131            } else if (offset < 0 || len < 0) {
    132                throw new IndexOutOfBoundsException();
    133            } else if (offset > b.length || offset + len > b.length) {
    134                throw new IndexOutOfBoundsException();
    135            } else if (len == 0) {
    136                return 0;
    137            } else {
    138                int readLen = 0;
    139                /*
    140                 Rationale for while-loop on (readLen == 0):
    141                 -----
    142                 Base32.readResults() usually returns > 0 or EOF (-1).  In the
    143                 rare case where it returns 0, we just keep trying.
    144
    145                 This is essentially an undocumented contract for InputStream
    146                 implementors that want their code to work properly with
    147                 java.io.InputStreamReader, since the latter hates it when
    148                 InputStream.read(byte[]) returns a zero.  Unfortunately our
    149                 readResults() call must return 0 if a large amount of the data
    150                 being decoded was non-base32, so this while-loop enables proper
    151                 interop with InputStreamReader for that scenario.
    152                 -----
    153                 This is a fix for CODEC-101
    154                */
    155                while (readLen == 0) {
    156                    if (!baseNCodec.hasData(context)) {
    157                        final byte[] buf = new byte[doEncode ? 4096 : 8192];
    158                        final int c = in.read(buf);
    159                        if (doEncode) {
    160                            baseNCodec.encode(buf, 0, c, context);
    161                        } else {
    162                            baseNCodec.decode(buf, 0, c, context);
    163                        }
    164                    }
    165                    readLen = baseNCodec.readResults(b, offset, len, context);
    166                }
    167                return readLen;
    168            }
    169        }
    170
    171        /**
    172         * Repositions this stream to the position at the time the mark method was last called on this input stream.
    173         * <p>
    174         * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
    175         *
    176         * @throws IOException if this method is invoked
    177         * @since 1.7
    178         */
    179        @Override
    180        public synchronized void reset() throws IOException {
    181            throw new IOException("mark/reset not supported");
    182        }
    183
    184        /**
    185         * {@inheritDoc}
    186         *
    187         * @throws IllegalArgumentException if the provided skip length is negative
    188         * @since 1.7
    189         */
    190        @Override
    191        public long skip(final long n) throws IOException {
    192            if (n < 0) {
    193                throw new IllegalArgumentException("Negative skip length: " + n);
    194            }
    195
    196            // skip in chunks of 512 bytes
    197            final byte[] b = new byte[512];
    198            long todo = n;
    199
    200            while (todo > 0) {
    201                int len = (int) Math.min(b.length, todo);
    202                len = this.read(b, 0, len);
    203                if (len == EOF) {
    204                    break;
    205                }
    206                todo -= len;
    207            }
    208
    209            return n - todo;
    210        }
    211    }