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 * @version $Id: BaseNCodecInputStream.java 1429868 2013-01-07 16:08:05Z ggregory $
033 */
034public 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}