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}