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}