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}