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        final byte[] result = new byte[length];
092        int read = 0;
093        while (read < length) {
094            final int count = is.read(result, read, length - read);
095            if (count < 0) {
096                throw new IOException(exception + " count: " + count
097                        + " read: " + read + " length: " + length);
098            }
099
100            read += count;
101        }
102
103        return result;
104    }
105
106    public static byte[] readBytes(final InputStream is, final int count) throws IOException {
107        return readBytes("", is, count, "Unexpected EOF");
108    }
109
110    public static void readAndVerifyBytes(final InputStream is, final byte[] expected,
111            final String exception) throws ImageReadException, IOException {
112        for (final byte element : expected) {
113            final int data = is.read();
114            final byte b = (byte) (0xff & data);
115
116            if (data < 0) {
117                throw new ImageReadException("Unexpected EOF.");
118            }
119
120            if (b != element) {
121                throw new ImageReadException(exception);
122            }
123        }
124    }
125
126    public static void readAndVerifyBytes(final InputStream is,
127            final BinaryConstant expected, final String exception)
128            throws ImageReadException, IOException {
129        for (int i = 0; i < expected.size(); i++) {
130            final int data = is.read();
131            final byte b = (byte) (0xff & data);
132
133            if (data < 0) {
134                throw new ImageReadException("Unexpected EOF.");
135            }
136
137            if (b != expected.get(i)) {
138                throw new ImageReadException(exception);
139            }
140        }
141    }
142
143    public static void skipBytes(final InputStream is, final long length, final String exception)
144            throws IOException {
145        long total = 0;
146        while (length != total) {
147            final long skipped = is.skip(length - total);
148            if (skipped < 1) {
149                throw new IOException(exception + " (" + skipped + ")");
150            }
151            total += skipped;
152        }
153    }
154
155    public static byte[] remainingBytes(final String name, final byte[] bytes, final int count) {
156        return slice(bytes, count, bytes.length - count);
157    }
158
159    public static byte[] slice(final byte[] bytes, final int start, final int count) {
160        final byte[] result = new byte[count];
161        System.arraycopy(bytes, start, result, 0, count);
162        return result;
163    }
164
165    public static byte[] head(final byte[] bytes, int count) {
166        if (count > bytes.length) {
167            count = bytes.length;
168        }
169        return slice(bytes, 0, count);
170    }
171
172    public static boolean compareBytes(final byte[] a, final int aStart, final byte[] b,
173            final int bStart, final int length) {
174        if (a.length < (aStart + length)) {
175            return false;
176        }
177        if (b.length < (bStart + length)) {
178            return false;
179        }
180
181        for (int i = 0; i < length; i++) {
182            if (a[aStart + i] != b[bStart + i]) {
183                return false;
184            }
185        }
186
187        return true;
188    }
189
190    public static int read4Bytes(final String name, final InputStream is,
191            final String exception, final ByteOrder byteOrder) throws IOException {
192        final int byte0 = is.read();
193        final int byte1 = is.read();
194        final int byte2 = is.read();
195        final int byte3 = is.read();
196        if ((byte0 | byte1 | byte2 | byte3) < 0) {
197            throw new IOException(exception);
198        }
199
200        final int result;
201        if (byteOrder == ByteOrder.BIG_ENDIAN) {
202            result = (byte0 << 24) | (byte1 << 16)
203                    | (byte2 << 8) | (byte3 << 0);
204        } else {
205            result = (byte3 << 24) | (byte2 << 16)
206                    | (byte1 << 8) | (byte0 << 0);
207        }
208
209        return result;
210    }
211
212    public static int read3Bytes(final String name, final InputStream is,
213            final String exception, final ByteOrder byteOrder) throws IOException {
214        final int byte0 = is.read();
215        final int byte1 = is.read();
216        final int byte2 = is.read();
217        if ((byte0 | byte1 | byte2) < 0) {
218            throw new IOException(exception);
219        }
220
221        final int result;
222        if (byteOrder == ByteOrder.BIG_ENDIAN) {
223            result = (byte0 << 16) | (byte1 << 8)
224                    | (byte2 << 0);
225        } else {
226            result = (byte2 << 16) | (byte1 << 8)
227                    | (byte0 << 0);
228        }
229
230        return result;
231    }
232
233    public static int read2Bytes(final String name, final InputStream is,
234            final String exception, final ByteOrder byteOrder) throws IOException {
235        final int byte0 = is.read();
236        final int byte1 = is.read();
237        if ((byte0 | byte1) < 0) {
238            throw new IOException(exception);
239        }
240
241        final int result;
242        if (byteOrder == ByteOrder.BIG_ENDIAN) {
243            result = (byte0 << 8) | byte1;
244        } else {
245            result = (byte1 << 8) | byte0;
246        }
247
248        return result;
249    }
250
251    public static void printCharQuad(final String msg, final int i) {
252        LOGGER.finest(msg + ": '" + (char) (0xff & (i >> 24))
253                + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
254                + (char) (0xff & (i >> 0)) + "'");
255
256    }
257
258    public static void printCharQuad(final PrintWriter pw, final String msg, final int i) {
259        pw.println(msg + ": '" + (char) (0xff & (i >> 24))
260                + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
261                + (char) (0xff & (i >> 0)) + "'");
262
263    }
264
265    public static void printByteBits(final String msg, final byte i) {
266        LOGGER.finest(msg + ": '" + Integer.toBinaryString(0xff & i));
267    }
268
269    public static int charsToQuad(final char c1, final char c2, final char c3, final char c4) {
270        return (((0xff & c1) << 24) | ((0xff & c2) << 16) | ((0xff & c3) << 8) | ((0xff & c4) << 0));
271    }
272
273    /**
274     * Convert a quad into a byte array.
275     * @param quad quad
276     * @return a byte array
277     */
278    public static byte[] quadsToByteArray(int quad) {
279        byte[] arr = new byte[4];
280        arr[0] = (byte) (quad >> 24);
281        arr[1] = (byte) (quad >> 16);
282        arr[2] = (byte) (quad >> 8);
283        arr[3] = (byte) quad;
284        return arr;
285    }
286
287    /**
288     * Consumes the {@code InputStream} (without closing it) searching for a quad. It will
289     * stop either when the quad is found, or when there are no more bytes in the input stream.
290     *
291     * <p>Returns {@code true} if it found the quad, and {@code false} otherwise.
292     *
293     * @param quad a quad (the needle)
294     * @param bis an input stream (the haystack)
295     * @return {@code true} if it found the quad, and {@code false} otherwise
296     * @throws IOException
297     */
298    public static boolean searchQuad(int quad, InputStream bis) throws IOException {
299        byte[] needle = BinaryFunctions.quadsToByteArray(quad);
300        int b = -1;
301        int position = 0;
302        while ((b = bis.read()) != -1) {
303            if (needle[position] == b) {
304                position++;
305                if (position == needle.length) {
306                    return true;
307                }
308            } else {
309                position = 0;
310            }
311        }
312        return false;
313    }
314
315    public static int findNull(final byte[] src) {
316        return findNull(src, 0);
317    }
318
319    public static int findNull(final byte[] src, final int start) {
320        for (int i = start; i < src.length; i++) {
321            if (src[i] == 0) {
322                return i;
323            }
324        }
325        return -1;
326    }
327
328    public static byte[] getRAFBytes(final RandomAccessFile raf, final long pos,
329            final int length, final String exception) throws IOException {
330        final byte[] result = new byte[length];
331
332        raf.seek(pos);
333
334        int read = 0;
335        while (read < length) {
336            final int count = raf.read(result, read, length - read);
337            if (count < 0) {
338                throw new IOException(exception);
339            }
340
341            read += count;
342        }
343
344        return result;
345
346    }
347
348    public static void skipBytes(final InputStream is, final long length) throws IOException {
349        skipBytes(is, length, "Couldn't skip bytes");
350    }
351
352    public static void copyStreamToStream(final InputStream is, final OutputStream os)
353            throws IOException {
354        final byte[] buffer = new byte[1024];
355        int read;
356        while ((read = is.read(buffer)) > 0) {
357            os.write(buffer, 0, read);
358        }
359    }
360
361    public static byte[] getStreamBytes(final InputStream is) throws IOException {
362        final ByteArrayOutputStream os = new ByteArrayOutputStream();
363        copyStreamToStream(is, os);
364        return os.toByteArray();
365    }
366}