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}