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