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