Source code
Description: source: package: org.apache.commons.codec.binary, class: BaseNCodecInputStream, class: AbstracBuilder

    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 *      https://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.ByteArrayInputStream;
    023import java.io.FilterInputStream;
    024import java.io.IOException;
    025import java.io.InputStream;
    026import java.util.Objects;
    027
    028import org.apache.commons.codec.binary.BaseNCodec.Context;
    029
    030/**
    031 * Abstracts Base-N input streams.
    032 *
    033 * @param <C> A BaseNCodec subclass.
    034 * @param <T> A BaseNCodecInputStream subclass.
    035 * @param <B> A subclass.
    036 * @see Base16InputStream
    037 * @see Base32InputStream
    038 * @see Base64InputStream
    039 * @since 1.5
    040 */
    041public class BaseNCodecInputStream<C extends BaseNCodec, T extends BaseNCodecInputStream<C, T, B>, B extends BaseNCodecInputStream.AbstracBuilder<T, C, B>>
    042        extends FilterInputStream {
    043
    044    /**
    045     * Builds input stream instances in {@link BaseNCodec} format.
    046     *
    047     * @param <T> The input stream type to build.
    048     * @param <C> A {@link BaseNCodec} subclass.
    049     * @param <B> The builder subclass.
    050     * @since 1.20.0
    051     */
    052    public abstract static class AbstracBuilder<T, C extends BaseNCodec, B extends AbstractBaseNCodecStreamBuilder<T, C, B>>
    053        extends AbstractBaseNCodecStreamBuilder<T, C, B> {
    054
    055        private InputStream inputStream;
    056
    057        /**
    058         * Constructs a new instance.
    059         */
    060        public AbstracBuilder() {
    061            // super
    062        }
    063
    064        /**
    065         * Gets the input stream.
    066         *
    067         * @return The input stream.
    068         */
    069        protected InputStream getInputStream() {
    070            return inputStream;
    071        }
    072
    073        /**
    074         * Sets the input bytes.
    075         *
    076         * @param inputBytes The input bytes.
    077         * @return {@code this} instance.
    078         * @since 1.22.0
    079         */
    080        public B setByteArray(final byte[] inputBytes) {
    081            return setInputStream(inputBytes == null ? null : new ByteArrayInputStream(inputBytes));
    082        }
    083
    084        /**
    085         * Sets the input stream.
    086         *
    087         * @param inputStream The input stream.
    088         * @return {@code this} instance.
    089         */
    090        public B setInputStream(final InputStream inputStream) {
    091            this.inputStream = inputStream;
    092            return asThis();
    093        }
    094    }
    095
    096    private final C baseNCodec;
    097    private final boolean doEncode;
    098    private final byte[] singleByte = new byte[1];
    099    private final byte[] buf;
    100    private final Context context = new Context();
    101
    102    /**
    103     * Constructs a new instance.
    104     *
    105     * @param builder A builder.
    106     * @since 1.20.0
    107     */
    108    @SuppressWarnings("resource") // Caller closes.
    109    protected BaseNCodecInputStream(final AbstracBuilder<T, C, B> builder) {
    110        super(builder.getInputStream());
    111        this.baseNCodec = builder.getBaseNCodec();
    112        this.doEncode = builder.getEncode();
    113        this.buf = new byte[doEncode ? 4096 : 8192];
    114    }
    115
    116    /**
    117     * Constructs a new instance.
    118     *
    119     * @param inputStream The input stream.
    120     * @param baseNCodec  The codec.
    121     * @param doEncode    set to true to perform encoding, else decoding.
    122     */
    123    protected BaseNCodecInputStream(final InputStream inputStream, final C baseNCodec, final boolean doEncode) {
    124        super(inputStream);
    125        this.doEncode = doEncode;
    126        this.baseNCodec = baseNCodec;
    127        this.buf = new byte[doEncode ? 4096 : 8192];
    128    }
    129
    130    /**
    131     * {@inheritDoc}
    132     *
    133     * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, {@code 1} otherwise.
    134     * @since 1.7
    135     */
    136    @Override
    137    public int available() throws IOException {
    138        // Note: The logic is similar to the InflaterInputStream:
    139        // as long as we have not reached EOF, indicate that there is more
    140        // data available. As we do not know for sure how much data is left,
    141        // just return 1 as a safe guess.
    142        return context.eof ? 0 : 1;
    143    }
    144
    145    /**
    146     * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
    147     *
    148     * <p>
    149     * The default is false for lenient encoding. Decoding will compose trailing bits into 8-bit bytes and discard the remainder.
    150     * </p>
    151     *
    152     * @return true if using strict decoding.
    153     * @since 1.15
    154     */
    155    public boolean isStrictDecoding() {
    156        return baseNCodec.isStrictDecoding();
    157    }
    158
    159    /**
    160     * Marks the current position in this input stream.
    161     * <p>
    162     * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
    163     * </p>
    164     *
    165     * @param readLimit The maximum limit of bytes that can be read before the mark position becomes invalid.
    166     * @see #markSupported()
    167     * @since 1.7
    168     */
    169    @Override
    170    public synchronized void mark(final int readLimit) {
    171        // noop
    172    }
    173
    174    /**
    175     * {@inheritDoc}
    176     *
    177     * @return Always returns {@code false}.
    178     */
    179    @Override
    180    public boolean markSupported() {
    181        return false; // not an easy job to support marks
    182    }
    183
    184    /**
    185     * Reads one {@code byte} from this input stream.
    186     *
    187     * @return The byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
    188     * @throws IOException if an I/O error occurs.
    189     */
    190    @Override
    191    public int read() throws IOException {
    192        int r = read(singleByte, 0, 1);
    193        while (r == 0) {
    194            r = read(singleByte, 0, 1);
    195        }
    196        if (r > 0) {
    197            final byte b = singleByte[0];
    198            return b < 0 ? 256 + b : b;
    199        }
    200        return EOF;
    201    }
    202
    203    /**
    204     * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} from this InputStream.
    205     *
    206     * @param array  destination byte array.
    207     * @param offset where to start writing the bytes.
    208     * @param len    maximum number of bytes to read.
    209     * @return number of bytes read.
    210     * @throws IOException               if an I/O error occurs.
    211     * @throws NullPointerException      if the byte array parameter is null.
    212     * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid.
    213     */
    214    @Override
    215    public int read(final byte[] array, final int offset, final int len) throws IOException {
    216        Objects.requireNonNull(array, "array");
    217        if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
    218            throw new IndexOutOfBoundsException();
    219        }
    220        if (len == 0) {
    221            return 0;
    222        }
    223        int readLen = 0;
    224        /*
    225         * Rationale for while-loop on (readLen == 0): ----- Base32.readResults() usually returns > 0 or EOF (-1). In the rare case where it returns 0, we just
    226         * keep trying.
    227         *
    228         * This is essentially an undocumented contract for InputStream implementers that want their code to work properly with java.io.InputStreamReader, since
    229         * the latter hates it when InputStream.read(byte[]) returns a zero. Unfortunately our readResults() call must return 0 if a large amount of the data
    230         * being decoded was non-base32, so this while-loop enables proper interop with InputStreamReader for that scenario. ----- This is a fix for CODEC-101
    231         */
    232        // Attempt to read the request length
    233        while (readLen < len) {
    234            if (!baseNCodec.hasData(context)) {
    235                // Obtain more data.
    236                // buf is reused across calls to read to avoid repeated allocations
    237                final int c = in.read(buf);
    238                if (doEncode) {
    239                    baseNCodec.encode(buf, 0, c, context);
    240                } else {
    241                    baseNCodec.decode(buf, 0, c, context);
    242                }
    243            }
    244            final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
    245            if (read < 0) {
    246                // Return the amount read or EOF
    247                return readLen != 0 ? readLen : -1;
    248            }
    249            readLen += read;
    250        }
    251        return readLen;
    252    }
    253
    254    /**
    255     * Repositions this stream to the position at the time the mark method was last called on this input stream.
    256     * <p>
    257     * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
    258     * </p>
    259     *
    260     * @throws IOException if this method is invoked.
    261     * @since 1.7
    262     */
    263    @Override
    264    public synchronized void reset() throws IOException {
    265        throw new IOException("mark/reset not supported");
    266    }
    267
    268    /**
    269     * {@inheritDoc}
    270     *
    271     * @throws IllegalArgumentException if the provided skip length is negative.
    272     * @since 1.7
    273     */
    274    @Override
    275    public long skip(final long n) throws IOException {
    276        if (n < 0) {
    277            throw new IllegalArgumentException("Negative skip length: " + n);
    278        }
    279        // skip in chunks of 512 bytes
    280        final byte[] b = new byte[512];
    281        long todo = n;
    282        while (todo > 0) {
    283            int len = (int) Math.min(b.length, todo);
    284            len = this.read(b, 0, len);
    285            if (len == EOF) {
    286                break;
    287            }
    288            todo -= len;
    289        }
    290        return n - todo;
    291    }
    292}