    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019
    020package org.apache.commons.compress.utils;
    021
    022import java.io.DataInput;
    023import java.io.DataOutput;
    024import java.io.IOException;
    025import java.io.InputStream;
    026import java.io.OutputStream;
    027
    028/**
    029 * Utility methods for reading and writing bytes.
    030 *
    031 * @since 1.14
    032 */
    033public final class ByteUtils {
    034
    035    /**
    036     * Used to consume bytes.
    037     *
    038     * @since 1.14
    039     */
    040    public interface ByteConsumer {
    041        /**
    042         * The contract is similar to {@link OutputStream#write(int)}, consume the lower eight bytes of the int as a byte.
    043         *
    044         * @param b the byte to consume
    045         * @throws IOException if consuming fails
    046         */
    047        void accept(int b) throws IOException;
    048    }
    049
    050    /**
    051     * Used to supply bytes.
    052     *
    053     * @since 1.14
    054     */
    055    public interface ByteSupplier {
    056        /**
    057         * The contract is similar to {@link InputStream#read()}, return the byte as an unsigned int, -1 if there are no more bytes.
    058         *
    059         * @return the supplied byte or -1 if there are no more bytes
    060         * @throws IOException if supplying fails
    061         */
    062        int getAsByte() throws IOException;
    063    }
    064
    065    /**
    066     * {@link ByteSupplier} based on {@link InputStream}.
    067     *
    068     * @since 1.14
    069     * @deprecated Unused
    070     */
    071    @Deprecated
    072    public static class InputStreamByteSupplier implements ByteSupplier {
    073        private final InputStream is;
    074
    075        /**
    076         * Constructs a new instance.
    077         *
    078         * @param is an input stream.
    079         */
    080        public InputStreamByteSupplier(final InputStream is) {
    081            this.is = is;
    082        }
    083
    084        @Override
    085        public int getAsByte() throws IOException {
    086            return is.read();
    087        }
    088    }
    089
    090    /**
    091     * {@link ByteConsumer} based on {@link OutputStream}.
    092     *
    093     * @since 1.14
    094     */
    095    public static class OutputStreamByteConsumer implements ByteConsumer {
    096        private final OutputStream os;
    097
    098        /**
    099         * Constructs a new instance.
    100         *
    101         * @param os an output stream.
    102         */
    103        public OutputStreamByteConsumer(final OutputStream os) {
    104            this.os = os;
    105        }
    106
    107        @Override
    108        public void accept(final int b) throws IOException {
    109            os.write(b);
    110        }
    111    }
    112
    113    /**
    114     * Empty array.
    115     *
    116     * @since 1.21
    117     */
    118    public static final byte[] EMPTY_BYTE_ARRAY = {};
    119
    120    private static void checkReadLength(final int length) {
    121        if (length > 8) {
    122            throw new IllegalArgumentException("Can't read more than eight bytes into a long value");
    123        }
    124    }
    125
    126    /**
    127     * Reads the given byte array as a little-endian long.
    128     *
    129     * @param bytes the byte array to convert
    130     * @return the number read
    131     */
    132    public static long fromLittleEndian(final byte[] bytes) {
    133        return fromLittleEndian(bytes, 0, bytes.length);
    134    }
    135
    136    /**
    137     * Reads the given byte array as a little-endian long.
    138     *
    139     * @param bytes  the byte array to convert
    140     * @param off    the offset into the array that starts the value
    141     * @param length the number of bytes representing the value
    142     * @return the number read
    143     * @throws IllegalArgumentException if len is bigger than eight
    144     */
    145    public static long fromLittleEndian(final byte[] bytes, final int off, final int length) {
    146        checkReadLength(length);
    147        long l = 0;
    148        for (int i = 0; i < length; i++) {
    149            l |= (bytes[off + i] & 0xffL) << 8 * i;
    150        }
    151        return l;
    152    }
    153
    154    /**
    155     * Reads the given number of bytes from the given supplier as a little-endian long.
    156     *
    157     * <p>
    158     * Typically used by our InputStreams that need to count the bytes read as well.
    159     * </p>
    160     *
    161     * @param supplier the supplier for bytes
    162     * @param length   the number of bytes representing the value
    163     * @return the number read
    164     * @throws IllegalArgumentException if len is bigger than eight
    165     * @throws IOException              if the supplier fails or doesn't supply the given number of bytes anymore
    166     */
    167    public static long fromLittleEndian(final ByteSupplier supplier, final int length) throws IOException {
    168        checkReadLength(length);
    169        long l = 0;
    170        for (int i = 0; i < length; i++) {
    171            final long b = supplier.getAsByte();
    172            if (b == -1) {
    173                throw new IOException("Premature end of data");
    174            }
    175            l |= b << i * 8;
    176        }
    177        return l;
    178    }
    179
    180    /**
    181     * Reads the given number of bytes from the given input as little-endian long.
    182     *
    183     * @param in     the input to read from
    184     * @param length the number of bytes representing the value
    185     * @return the number read
    186     * @throws IllegalArgumentException if len is bigger than eight
    187     * @throws IOException              if reading fails or the stream doesn't contain the given number of bytes anymore
    188     */
    189    public static long fromLittleEndian(final DataInput in, final int length) throws IOException {
    190        // somewhat duplicates the ByteSupplier version in order to save the creation of a wrapper object
    191        checkReadLength(length);
    192        long l = 0;
    193        for (int i = 0; i < length; i++) {
    194            final long b = in.readUnsignedByte();
    195            l |= b << i * 8;
    196        }
    197        return l;
    198    }
    199
    200    /**
    201     * Reads the given number of bytes from the given stream as a little-endian long.
    202     *
    203     * @param in     the stream to read from
    204     * @param length the number of bytes representing the value
    205     * @return the number read
    206     * @throws IllegalArgumentException if len is bigger than eight
    207     * @throws IOException              if reading fails or the stream doesn't contain the given number of bytes anymore
    208     * @deprecated Unused.
    209     */
    210    @Deprecated
    211    public static long fromLittleEndian(final InputStream in, final int length) throws IOException {
    212        // somewhat duplicates the ByteSupplier version in order to save the creation of a wrapper object
    213        checkReadLength(length);
    214        long l = 0;
    215        for (int i = 0; i < length; i++) {
    216            final long b = in.read();
    217            if (b == -1) {
    218                throw new IOException("Premature end of data");
    219            }
    220            l |= b << i * 8;
    221        }
    222        return l;
    223    }
    224
    225    /**
    226     * Inserts the given value into the array as a little-endian sequence of the given length starting at the given offset.
    227     *
    228     * @param b      the array to write into
    229     * @param value  the value to insert
    230     * @param off    the offset into the array that receives the first byte
    231     * @param length the number of bytes to use to represent the value
    232     */
    233    public static void toLittleEndian(final byte[] b, final long value, final int off, final int length) {
    234        long num = value;
    235        for (int i = 0; i < length; i++) {
    236            b[off + i] = (byte) (num & 0xff);
    237            num >>= 8;
    238        }
    239    }
    240
    241    /**
    242     * Provides the given value to the given consumer as a little-endian sequence of the given length.
    243     *
    244     * @param consumer the consumer to provide the bytes to
    245     * @param value    the value to provide
    246     * @param length   the number of bytes to use to represent the value
    247     * @throws IOException if writing fails
    248     */
    249    public static void toLittleEndian(final ByteConsumer consumer, final long value, final int length) throws IOException {
    250        long num = value;
    251        for (int i = 0; i < length; i++) {
    252            consumer.accept((int) (num & 0xff));
    253            num >>= 8;
    254        }
    255    }
    256
    257    /**
    258     * Writes the given value to the given stream as a little-endian array of the given length.
    259     *
    260     * @param out    the output to write to
    261     * @param value  the value to write
    262     * @param length the number of bytes to use to represent the value
    263     * @throws IOException if writing fails
    264     * @deprecated Unused.
    265     */
    266    @Deprecated
    267    public static void toLittleEndian(final DataOutput out, final long value, final int length) throws IOException {
    268        // somewhat duplicates the ByteConsumer version in order to save the creation of a wrapper object
    269        long num = value;
    270        for (int i = 0; i < length; i++) {
    271            out.write((int) (num & 0xff));
    272            num >>= 8;
    273        }
    274    }
    275
    276    /**
    277     * Writes the given value to the given stream as a little-endian array of the given length.
    278     *
    279     * @param out    the stream to write to
    280     * @param value  the value to write
    281     * @param length the number of bytes to use to represent the value
    282     * @throws IOException if writing fails
    283     */
    284    public static void toLittleEndian(final OutputStream out, final long value, final int length) throws IOException {
    285        // somewhat duplicates the ByteConsumer version in order to save the creation of a wrapper object
    286        long num = value;
    287        for (int i = 0; i < length; i++) {
    288            out.write((int) (num & 0xff));
    289            num >>= 8;
    290        }
    291    }
    292
    293    private ByteUtils() {
    294        /* no instances */ }
    295}