    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.ByteArrayOutputStream;
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.OutputStream;
    023import java.io.PrintWriter;
    024import java.io.RandomAccessFile;
    025import java.nio.ByteOrder;
    026import java.util.logging.Logger;
    027
    028import org.apache.commons.imaging.ImageReadException;
    029
    030/**
    031 * Convenience methods for various binary and I/O operations.
    032 */
    033public final class BinaryFunctions {
    034
    035    private static final Logger LOGGER = Logger.getLogger(BinaryFunctions.class.getName());
    036
    037    private BinaryFunctions() {
    038    }
    039
    040    public static boolean startsWith(final byte[] haystack, final byte[] needle) {
    041        if (needle == null) {
    042            return false;
    043        }
    044        if (haystack == null) {
    045            return false;
    046        }
    047        if (needle.length > haystack.length) {
    048            return false;
    049        }
    050
    051        for (int i = 0; i < needle.length; i++) {
    052            if (needle[i] != haystack[i]) {
    053                return false;
    054            }
    055        }
    056
    057        return true;
    058    }
    059
    060    public static boolean startsWith(final byte[] haystack, final BinaryConstant needle) {
    061        if ((haystack == null) || (haystack.length < needle.size())) {
    062            return false;
    063        }
    064
    065        for (int i = 0; i < needle.size(); i++) {
    066            if (haystack[i] != needle.get(i)) {
    067                return false;
    068            }
    069        }
    070
    071        return true;
    072    }
    073
    074    public static byte readByte(final String name, final InputStream is, final String exception)
    075            throws IOException {
    076        final int result = is.read();
    077        if ((result < 0)) {
    078            throw new IOException(exception);
    079        }
    080        return (byte) (0xff & result);
    081    }
    082
    083    public static byte[] readBytes(final String name, final InputStream is, final int length)
    084            throws IOException {
    085        final String exception = name + " could not be read.";
    086        return readBytes(name, is, length, exception);
    087    }
    088
    089    public static byte[] readBytes(final String name, final InputStream is, final int length,
    090            final String exception) throws IOException {
    091        if (length < 0) {
    092            throw new IOException(String.format("%s, invalid length: %d", exception, length));
    093        }
    094        final byte[] result = new byte[length];
    095        int read = 0;
    096        while (read < length) {
    097            final int count = is.read(result, read, length - read);
    098            if (count < 0) {
    099                throw new IOException(exception + " count: " + count
    100                        + " read: " + read + " length: " + length);
    101            }
    102
    103            read += count;
    104        }
    105
    106        return result;
    107    }
    108
    109    public static byte[] readBytes(final InputStream is, final int count) throws IOException {
    110        return readBytes("", is, count, "Unexpected EOF");
    111    }
    112
    113    public static void readAndVerifyBytes(final InputStream is, final byte[] expected,
    114            final String exception) throws ImageReadException, IOException {
    115        for (final byte element : expected) {
    116            final int data = is.read();
    117            final byte b = (byte) (0xff & data);
    118
    119            if (data < 0) {
    120                throw new ImageReadException("Unexpected EOF.");
    121            }
    122
    123            if (b != element) {
    124                throw new ImageReadException(exception);
    125            }
    126        }
    127    }
    128
    129    public static void readAndVerifyBytes(final InputStream is,
    130            final BinaryConstant expected, final String exception)
    131            throws ImageReadException, IOException {
    132        for (int i = 0; i < expected.size(); i++) {
    133            final int data = is.read();
    134            final byte b = (byte) (0xff & data);
    135
    136            if (data < 0) {
    137                throw new ImageReadException("Unexpected EOF.");
    138            }
    139
    140            if (b != expected.get(i)) {
    141                throw new ImageReadException(exception);
    142            }
    143        }
    144    }
    145
    146    public static void skipBytes(final InputStream is, final long length, final String exception)
    147            throws IOException {
    148        long total = 0;
    149        while (length != total) {
    150            final long skipped = is.skip(length - total);
    151            if (skipped < 1) {
    152                throw new IOException(exception + " (" + skipped + ")");
    153            }
    154            total += skipped;
    155        }
    156    }
    157
    158    public static byte[] remainingBytes(final String name, final byte[] bytes, final int count) {
    159        return slice(bytes, count, bytes.length - count);
    160    }
    161
    162    public static byte[] slice(final byte[] bytes, final int start, final int count) {
    163        final byte[] result = new byte[count];
    164        System.arraycopy(bytes, start, result, 0, count);
    165        return result;
    166    }
    167
    168    public static byte[] head(final byte[] bytes, int count) {
    169        if (count > bytes.length) {
    170            count = bytes.length;
    171        }
    172        return slice(bytes, 0, count);
    173    }
    174
    175    public static boolean compareBytes(final byte[] a, final int aStart, final byte[] b,
    176            final int bStart, final int length) {
    177        if (a.length < (aStart + length)) {
    178            return false;
    179        }
    180        if (b.length < (bStart + length)) {
    181            return false;
    182        }
    183
    184        for (int i = 0; i < length; i++) {
    185            if (a[aStart + i] != b[bStart + i]) {
    186                return false;
    187            }
    188        }
    189
    190        return true;
    191    }
    192
    193    public static int read4Bytes(final String name, final InputStream is,
    194            final String exception, final ByteOrder byteOrder) throws IOException {
    195        final int byte0 = is.read();
    196        final int byte1 = is.read();
    197        final int byte2 = is.read();
    198        final int byte3 = is.read();
    199        if ((byte0 | byte1 | byte2 | byte3) < 0) {
    200            throw new IOException(exception);
    201        }
    202
    203        final int result;
    204        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    205            result = (byte0 << 24) | (byte1 << 16)
    206                    | (byte2 << 8) | (byte3 << 0);
    207        } else {
    208            result = (byte3 << 24) | (byte2 << 16)
    209                    | (byte1 << 8) | (byte0 << 0);
    210        }
    211
    212        return result;
    213    }
    214
    215    public static int read3Bytes(final String name, final InputStream is,
    216            final String exception, final ByteOrder byteOrder) throws IOException {
    217        final int byte0 = is.read();
    218        final int byte1 = is.read();
    219        final int byte2 = is.read();
    220        if ((byte0 | byte1 | byte2) < 0) {
    221            throw new IOException(exception);
    222        }
    223
    224        final int result;
    225        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    226            result = (byte0 << 16) | (byte1 << 8)
    227                    | (byte2 << 0);
    228        } else {
    229            result = (byte2 << 16) | (byte1 << 8)
    230                    | (byte0 << 0);
    231        }
    232
    233        return result;
    234    }
    235
    236    public static int read2Bytes(final String name, final InputStream is,
    237            final String exception, final ByteOrder byteOrder) throws IOException {
    238        final int byte0 = is.read();
    239        final int byte1 = is.read();
    240        if ((byte0 | byte1) < 0) {
    241            throw new IOException(exception);
    242        }
    243
    244        final int result;
    245        if (byteOrder == ByteOrder.BIG_ENDIAN) {
    246            result = (byte0 << 8) | byte1;
    247        } else {
    248            result = (byte1 << 8) | byte0;
    249        }
    250
    251        return result;
    252    }
    253
    254    public static void printCharQuad(final String msg, final int i) {
    255        LOGGER.finest(msg + ": '" + (char) (0xff & (i >> 24))
    256                + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
    257                + (char) (0xff & (i >> 0)) + "'");
    258
    259    }
    260
    261    public static void printCharQuad(final PrintWriter pw, final String msg, final int i) {
    262        pw.println(msg + ": '" + (char) (0xff & (i >> 24))
    263                + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
    264                + (char) (0xff & (i >> 0)) + "'");
    265
    266    }
    267
    268    public static void printByteBits(final String msg, final byte i) {
    269        LOGGER.finest(msg + ": '" + Integer.toBinaryString(0xff & i));
    270    }
    271
    272    public static int charsToQuad(final char c1, final char c2, final char c3, final char c4) {
    273        return (((0xff & c1) << 24) | ((0xff & c2) << 16) | ((0xff & c3) << 8) | ((0xff & c4) << 0));
    274    }
    275
    276    /**
    277     * Convert a quad into a byte array.
    278     * @param quad quad
    279     * @return a byte array
    280     */
    281    public static byte[] quadsToByteArray(final int quad) {
    282        final byte[] arr = new byte[4];
    283        arr[0] = (byte) (quad >> 24);
    284        arr[1] = (byte) (quad >> 16);
    285        arr[2] = (byte) (quad >> 8);
    286        arr[3] = (byte) quad;
    287        return arr;
    288    }
    289
    290    /**
    291     * Consumes the {@code InputStream} (without closing it) searching for a quad. It will
    292     * stop either when the quad is found, or when there are no more bytes in the input stream.
    293     *
    294     * <p>Returns {@code true} if it found the quad, and {@code false} otherwise.
    295     *
    296     * @param quad a quad (the needle)
    297     * @param bis an input stream (the haystack)
    298     * @return {@code true} if it found the quad, and {@code false} otherwise
    299     * @throws IOException if it fails to read from the given input stream
    300     */
    301    public static boolean searchQuad(final int quad, final InputStream bis) throws IOException {
    302        final byte[] needle = BinaryFunctions.quadsToByteArray(quad);
    303        int b = -1;
    304        int position = 0;
    305        while ((b = bis.read()) != -1) {
    306            if (needle[position] == b) {
    307                position++;
    308                if (position == needle.length) {
    309                    return true;
    310                }
    311            } else {
    312                position = 0;
    313            }
    314        }
    315        return false;
    316    }
    317
    318    public static int findNull(final byte[] src) {
    319        return findNull(src, 0);
    320    }
    321
    322    public static int findNull(final byte[] src, final int start) {
    323        for (int i = start; i < src.length; i++) {
    324            if (src[i] == 0) {
    325                return i;
    326            }
    327        }
    328        return -1;
    329    }
    330
    331    public static byte[] getRAFBytes(final RandomAccessFile raf, final long pos,
    332            final int length, final String exception) throws IOException {
    333        if (length < 0) {
    334            throw new IOException(String.format("%s, invalid length: %d", exception, length));
    335        }
    336        final byte[] result = new byte[length];
    337
    338        raf.seek(pos);
    339
    340        int read = 0;
    341        while (read < length) {
    342            final int count = raf.read(result, read, length - read);
    343            if (count < 0) {
    344                throw new IOException(exception);
    345            }
    346
    347            read += count;
    348        }
    349
    350        return result;
    351
    352    }
    353
    354    public static void skipBytes(final InputStream is, final long length) throws IOException {
    355        skipBytes(is, length, "Couldn't skip bytes");
    356    }
    357
    358    public static void copyStreamToStream(final InputStream is, final OutputStream os)
    359            throws IOException {
    360        final byte[] buffer = new byte[1024];
    361        int read;
    362        while ((read = is.read(buffer)) > 0) {
    363            os.write(buffer, 0, read);
    364        }
    365    }
    366
    367    public static byte[] getStreamBytes(final InputStream is) throws IOException {
    368        final ByteArrayOutputStream os = new ByteArrayOutputStream();
    369        copyStreamToStream(is, os);
    370        return os.toByteArray();
    371    }
    372}