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}