    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 */
    017package org.apache.commons.imaging.common;
    018
    019import java.io.IOException;
    020import java.io.InputStream;
    021import java.io.PrintWriter;
    022import java.io.RandomAccessFile;
    023import java.nio.ByteOrder;
    024import java.util.Arrays;
    025import java.util.logging.Logger;
    026
    027import org.apache.commons.imaging.ImagingException;
    028import org.apache.commons.io.IOUtils;
    029import org.apache.commons.io.RandomAccessFiles;
    030import org.apache.commons.lang3.ArrayUtils;
    031
    032/**
    033 * Convenience methods for various binary and I/O operations.
    034 */
    035public final class BinaryFunctions {
    036
    037    private static final byte NUL = (byte) 0;
    038    private static final Logger LOGGER = Logger.getLogger(BinaryFunctions.class.getName());
    039
    040    public static int charsToQuad(final char c1, final char c2, final char c3, final char c4) {
    041        return (0xff & c1) << 24 | (0xff & c2) << 16 | (0xff & c3) << 8 | (0xff & c4) << 0;
    042    }
    043
    044    public static boolean compareBytes(final byte[] a, final int aStart, final byte[] b, final int bStart, final int length) {
    045        if (a.length < aStart + length) {
    046            return false;
    047        }
    048        if (b.length < bStart + length) {
    049            return false;
    050        }
    051        for (int i = 0; i < length; i++) {
    052            if (a[aStart + i] != b[bStart + i]) {
    053                return false;
    054            }
    055        }
    056        return true;
    057    }
    058
    059    /**
    060     * Copies the specified range of the specified array into a new array.
    061     *
    062     * @param original the array from which a range is to be copied.
    063     * @param from     the initial index of the range to be copied, inclusive.
    064     * @param count    the amount of bytes to copy.
    065     * @return a new array containing the specified range from the original array, truncated or padded with zeros to obtain the required length.
    066     */
    067    public static byte[] copyOfRange(final byte[] original, final int from, final int count) {
    068        return Arrays.copyOfRange(original, from, from + Allocator.checkByteArray(count));
    069    }
    070
    071    /**
    072     * Copies the start of the specified array into a new array.
    073     *
    074     * @param original the array from which a range is to be copied.
    075     * @param count    the amount of bytes to copy.
    076     * @return a new array containing the specified range from the original array, truncated or padded with zeros to obtain the required length.
    077     */
    078    public static byte[] copyOfStart(final byte[] original, int count) {
    079        if (count > original.length) {
    080            count = original.length;
    081        }
    082        return copyOfRange(original, 0, count);
    083    }
    084
    085    public static byte[] getBytes(final RandomAccessFile raf, final long pos, final int length, final String exception) throws IOException {
    086        if (length < 0) {
    087            throw new IOException(String.format("%s, invalid length: %d", exception, length));
    088        }
    089        Allocator.checkByteArray(length);
    090        return RandomAccessFiles.read(raf, pos, length);
    091
    092    }
    093
    094    /**
    095     * Finds the index of the first 0 in the array starting at the given index.
    096     *
    097     * @param src  the array to search for the object, may be {@code null}
    098     * @param start  the index to start searching at
    099     * @param message The ImagingException message if 0 is not found.
    100     * @return the index of the value within the array,
    101     * @throws ImagingException Thrown if 0 is not found.
    102     */
    103    public static int indexOf0(final byte[] src, final int start, final String message) throws ImagingException {
    104        final int i = ArrayUtils.indexOf(src, NUL, start);
    105        if (i < 0) {
    106            throw new ImagingException(message);
    107        }
    108        return i;
    109    }
    110
    111    /**
    112     * Finds the index of the first 0 in the array starting at the given index.
    113     *
    114     * @param src  the array to search for the object, may be {@code null}
    115     * @param message The ImagingException message if 0 is not found.
    116     * @return the index of the value within the array,
    117     * @throws ImagingException Thrown if 0 is not found.
    118     */
    119    public static int indexOf0(final byte[] src, final String message) throws ImagingException {
    120        return indexOf0(src, 0, message);
    121    }
    122
    123    public static void logByteBits(final String msg, final byte i) {
    124        LOGGER.finest(msg + ": '" + Integer.toBinaryString(0xff & i));
    125    }
    126
    127    public static void logCharQuad(final String msg, final int i) {
    128        LOGGER.finest(msg + ": '" + (char) (0xff & i >> 24) + (char) (0xff & i >> 16) + (char) (0xff & i >> 8) + (char) (0xff & i >> 0) + "'");
    129
    130    }
    131
    132    public static void printCharQuad(final PrintWriter pw, final String msg, final int i) {
    133        pw.println(msg + ": '" + (char) (0xff & i >> 24) + (char) (0xff & i >> 16) + (char) (0xff & i >> 8) + (char) (0xff & i >> 0) + "'");
    134    }
    135
    136    /**
    137     * Convert a quad into a byte array.
    138     *
    139     * @param quad quad
    140     * @return a byte array
    141     */
    142    public static byte[] quadsToByteArray(final int quad) {
    143        final byte[] arr = new byte[4];
    144        arr[0] = (byte) (quad >> 24);
    145        arr[1] = (byte) (quad >> 16);
    146        arr[2] = (byte) (quad >> 8);
    147        arr[3] = (byte) quad;
    148        return arr;
    149    }
    150
    151    public static int read2Bytes(final String name, final InputStream in, final String exception, final ByteOrder byteOrder) throws IOException {
    152        final int byte0 = in.read();
    153        final int byte1 = in.read();
    154        if ((byte0 | byte1) < 0) {
    155            throw new IOException(exception);
    156        }
    157        final int result;
    158        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    159            result = byte0 << 8 | byte1;
    160        } else {
    161            result = byte1 << 8 | byte0;
    162        }
    163        return result;
    164    }
    165
    166    public static int read3Bytes(final String name, final InputStream in, final String exception, final ByteOrder byteOrder) throws IOException {
    167        final int byte0 = in.read();
    168        final int byte1 = in.read();
    169        final int byte2 = in.read();
    170        if ((byte0 | byte1 | byte2) < 0) {
    171            throw new IOException(exception);
    172        }
    173        final int result;
    174        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    175            result = byte0 << 16 | byte1 << 8 | byte2 << 0;
    176        } else {
    177            result = byte2 << 16 | byte1 << 8 | byte0 << 0;
    178        }
    179        return result;
    180    }
    181
    182    public static int read4Bytes(final String name, final InputStream in, final String exception, final ByteOrder byteOrder) throws IOException {
    183        final int byte0 = in.read();
    184        final int byte1 = in.read();
    185        final int byte2 = in.read();
    186        final int byte3 = in.read();
    187        if ((byte0 | byte1 | byte2 | byte3) < 0) {
    188            throw new IOException(exception);
    189        }
    190        final int result;
    191        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    192            result = byte0 << 24 | byte1 << 16 | byte2 << 8 | byte3 << 0;
    193        } else {
    194            result = byte3 << 24 | byte2 << 16 | byte1 << 8 | byte0 << 0;
    195        }
    196        return result;
    197    }
    198
    199    /**
    200     * Reads eight bytes from the specified input stream, adjust for byte order, and return a long integer.
    201     *
    202     * @param name      a descriptive identifier used for diagnostic purposes
    203     * @param in        a valid input stream
    204     * @param exception application-defined message to be used for constructing an exception if an error condition is triggered.
    205     * @param byteOrder the order in which the InputStream marshals data
    206     * @return a long integer interpreted from next 8 bytes in the InputStream
    207     * @throws IOException in the event of a non-recoverable error, such as an attempt to read past the end of file.
    208     */
    209    public static long read8Bytes(final String name, final InputStream in, final String exception, final ByteOrder byteOrder) throws IOException {
    210        final long byte0 = in.read();
    211        final long byte1 = in.read();
    212        final long byte2 = in.read();
    213        final long byte3 = in.read();
    214        final long byte4 = in.read();
    215        final long byte5 = in.read();
    216        final long byte6 = in.read();
    217        final long byte7 = in.read();
    218        if ((byte0 | byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7) < 0) {
    219            throw new IOException(exception);
    220        }
    221        final long result;
    222        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    223            result = byte0 << 56 | byte1 << 48 | byte2 << 40 | byte3 << 32 | byte4 << 24 | byte5 << 16 | byte6 << 8 | byte7 << 0;
    224        } else {
    225            result = byte7 << 56 | byte6 << 48 | byte5 << 40 | byte4 << 32 | byte3 << 24 | byte2 << 16 | byte1 << 8 | byte0 << 0;
    226        }
    227        return result;
    228    }
    229
    230    public static void readAndVerifyBytes(final InputStream in, final BinaryConstant expected, final String exception) throws ImagingException, IOException {
    231        readAndVerifyBytes(in, expected.rawValue(), exception);
    232    }
    233
    234    public static void readAndVerifyBytes(final InputStream in, final byte[] expected, final String exception) throws ImagingException, IOException {
    235        for (final byte element : expected) {
    236            final int data = in.read();
    237            final byte b = (byte) (0xff & data);
    238            if (data < 0) {
    239                throw new ImagingException("Unexpected EOF.");
    240            }
    241            if (b != element) {
    242                throw new ImagingException(exception);
    243            }
    244        }
    245    }
    246
    247    public static byte readByte(final String name, final InputStream in, final String exceptionMessage) throws IOException {
    248        final int result = in.read();
    249        if (result < 0) {
    250            throw new IOException(exceptionMessage);
    251        }
    252        return (byte) (0xff & result);
    253    }
    254
    255    public static byte[] readBytes(final InputStream in, final int count) throws IOException {
    256        return readBytes("", in, count, "Unexpected EOF");
    257    }
    258
    259    public static byte[] readBytes(final String name, final InputStream in, final int length) throws IOException {
    260        return readBytes(name, in, length, name + " could not be read.");
    261    }
    262
    263    public static byte[] readBytes(final String name, final InputStream in, final int length, final String exception) throws IOException {
    264        try {
    265            return IOUtils.toByteArray(in, Allocator.check(length));
    266        } catch (final IOException e) {
    267            throw new IOException(exception + ", name: " + name + ", length: " + length);
    268        }
    269    }
    270
    271    public static byte[] remainingBytes(final String name, final byte[] bytes, final int count) {
    272        return copyOfRange(bytes, count, bytes.length - count);
    273    }
    274
    275    /**
    276     * Consumes the {@code InputStream} (without closing it) searching for a quad. It will stop either when the quad is found, or when there are no more bytes
    277     * in the input stream.
    278     *
    279     * <p>
    280     * Returns {@code true} if it found the quad, and {@code false} otherwise.
    281     *
    282     * @param quad a quad (the needle)
    283     * @param in  an input stream (the haystack)
    284     * @return {@code true} if it found the quad, and {@code false} otherwise
    285     * @throws IOException if it fails to read from the given input stream
    286     */
    287    public static boolean searchQuad(final int quad, final InputStream in) throws IOException {
    288        final byte[] needle = quadsToByteArray(quad);
    289        int b = -1;
    290        int position = 0;
    291        while ((b = in.read()) != -1) {
    292            if (needle[position] == b) {
    293                position++;
    294                if (position == needle.length) {
    295                    return true;
    296                }
    297            } else {
    298                position = 0;
    299            }
    300        }
    301        return false;
    302    }
    303
    304    public static long skipBytes(final InputStream in, final long skip) throws IOException {
    305        return skipBytes(in, skip, "Couldn't skip bytes");
    306    }
    307
    308    public static long skipBytes(final InputStream in, final long skip, final String exMessage) throws IOException {
    309        try {
    310            return IOUtils.skip(in, skip);
    311        } catch (final IOException e) {
    312            throw new IOException(exMessage, e);
    313        }
    314    }
    315
    316    public static boolean startsWith(final byte[] buffer, final byte[] search) {
    317        if (search == null) {
    318            return false;
    319        }
    320        if (buffer == null) {
    321            return false;
    322        }
    323        if (search.length > buffer.length) {
    324            return false;
    325        }
    326        for (int i = 0; i < search.length; i++) {
    327            if (search[i] != buffer[i]) {
    328                return false;
    329            }
    330        }
    331        return true;
    332    }
    333
    334    private BinaryFunctions() {
    335    }
    336}