All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.io.ByteArrayUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.OptionalLong;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.filesystem.FileSystemUtils;
import org.conqat.lib.commons.string.StringUtils;

/**
 * Utility methods for dealing with raw byte arrays. This is located in the I/O package, as the
 * typical application for these methods is binary I/O on byte array level.
 */
public class ByteArrayUtils {

	/**
	 * An empty byte array. Analog to {@link StringUtils#EMPTY_STRING}.
	 */
	public static final byte[] EMPTY_ARRAY = {};

	/**
	 * Converts an integer value to a byte array. The returned array has a length of
	 * {@link Integer#BYTES}.
	 */
	public static byte[] intToByteArray(int value) {
		byte[] bytes = new byte[Integer.BYTES];
		storeIntInStartOfArray(value, bytes);
		return bytes;
	}

	/**
	 * Converts an array of integer values to an array of bytes by converting each int to its 4 byte
	 * representation and concatenating the results.
	 */
	public static byte[] intsToByteArray(int... values) {
		byte[] bytes = new byte[Integer.BYTES * values.length];
		for (int i = 0; i < values.length; ++i) {
			storeIntInArray(values[i], bytes, i * Integer.BYTES);
		}
		return bytes;
	}

	/** Stores the given int at the first 4 bytes of the array. */
	public static void storeIntInStartOfArray(int value, byte[] bytes) {
		storeIntInArray(value, bytes, 0);
	}

	/** Stores the given int at the first 4 bytes of the array. */
	public static void storeIntInArray(int value, byte[] bytes, int startOffset) {
		bytes[startOffset] = (byte) (value >> 24);
		bytes[startOffset + 1] = (byte) (value >> 16);
		bytes[startOffset + 2] = (byte) (value >> 8);
		bytes[startOffset + 3] = (byte) (value);
	}

	/**
	 * Converts a double value to a byte array. The returned array has a length of {@link Double#BYTES}
	 */
	public static byte[] doubleToByteArray(double value) {
		long longBits = Double.doubleToRawLongBits(value);
		return ByteArrayUtils.longToByteArray(longBits);
	}

	/**
	 * Converts a long value to a byte array. The returned array has a length of {@link Long#BYTES}
	 */
	public static byte[] longToByteArray(long value) {
		byte[] bytes = new byte[Long.BYTES];
		putLongIntoByteArray(bytes, 0, value);
		return bytes;
	}

	/**
	 * Converts a byte array to an integer value.
	 * 

* Overall, this method is only guaranteed to work if the input array was created by * {@link #intToByteArray(int)}. */ public static int byteArrayToInt(byte[] bytes) { CCSMAssert.isTrue(bytes.length == Integer.BYTES, "bytes.length must be 4"); return readIntFromStartOfArray(bytes); } /** * Reads an int stored with {@link #storeIntInStartOfArray(int, byte[])} from the first 4 bytes of * the array. */ public static int readIntFromStartOfArray(byte[] bytes) { return getIntFromByteArray(bytes, 0); } /** * Converts a byte array to a long value. *

* Overall, this method is only guaranteed to work if the input array was created by * {@link #longToByteArray(long)}. */ public static long byteArrayToLong(byte[] bytes) { CCSMAssert.isTrue(bytes.length == Long.BYTES, "bytes.length must be 8"); return getLongFromByteArray(bytes, 0); } /** * Converts a byte array to a double value. *

* Overall, this method is only guaranteed to work if the input array was created by * {@link #doubleToByteArray(double)}. */ public static double byteArrayToDouble(byte[] value) { long longBits = ByteArrayUtils.byteArrayToLong(value); return Double.longBitsToDouble(longBits); } /** * Converts a byte array to an optional long value, by mapping a null input array to empty. */ public static OptionalLong byteArrayToOptionalLong(byte[] bytes) { if (bytes == null) { return OptionalLong.empty(); } return OptionalLong.of(byteArrayToLong(bytes)); } /** * Decompresses a single byte[] using GZIP. A null input array will cause this method to return * null. * * @throws IOException * if the input array is not valid GZIP compressed data (as created by * {@link #compress(byte[])}). */ public static byte[] decompress(byte[] value) throws IOException { if (value == null) { return null; } ByteArrayOutputStream bos = new ByteArrayOutputStream(value.length); ByteArrayInputStream bis = new ByteArrayInputStream(value); GZIPInputStream gzis = new GZIPInputStream(bis); FileSystemUtils.copy(gzis, bos); // it does not matter if we close in case of exceptions, as these are // in-memory resources gzis.close(); bos.close(); return bos.toByteArray(); } /** * Compresses a single byte[] using GZIP. A null input array will cause this method to return null. */ public static byte[] compress(byte[] value) { if (value == null) { return null; } ByteArrayOutputStream bos = new ByteArrayOutputStream(value.length); try { GZIPOutputStream gzos = new GZIPOutputStream(bos); gzos.write(value); // it does not matter if we close in case of exceptions, as this is // an in-memory resource gzos.close(); } catch (IOException e) { throw new AssertionError("Can not happen as we work in memory: " + e.getMessage()); } return bos.toByteArray(); } /** Returns whether the prefix is a prefix of the given key. */ public static boolean isPrefix(byte[] prefix, byte[] key) { return isPrefix(prefix, key, 0); } /** * Returns whether the prefix is a prefix of the given key when only * looking at the part of key starting at startIndex. */ public static boolean isPrefix(byte[] prefix, byte[] key, int startIndex) { if (key.length - startIndex < prefix.length) { return false; } for (int i = 0; i < prefix.length; ++i) { if (prefix[i] != key[i + startIndex]) { return false; } } return true; } /** Returns true if a1 is (lexicographically) less than a2. */ public static boolean isLess(byte[] a1, byte[] a2, boolean resultIfEqual) { int limit = Math.min(a1.length, a2.length); for (int i = 0; i < limit; ++i) { if (unsignedByte(a1[i]) < unsignedByte(a2[i])) { return true; } if (unsignedByte(a1[i]) > unsignedByte(a2[i])) { return false; } } if (a1.length < a2.length) { return true; } if (a1.length > a2.length) { return false; } return resultIfEqual; } /** Returns the unsigned byte interpretation of the parameter. */ public static int unsignedByte(byte b) { return b & 0xff; } /** Returns the unsigned byte interpretation of the parameter as long. */ public static long unsignedByteAsLong(byte b) { return b & 0xffL; } /** Returns the concatenation of the given arrays. */ public static byte[] concat(byte[]... arrays) { int length = 0; for (byte[] array : arrays) { length += array.length; } byte[] result = new byte[length]; int start = 0; for (byte[] array : arrays) { System.arraycopy(array, 0, result, start, array.length); start += array.length; } return result; } /** Returns the concatenation of the given arrays. */ public static byte[] concat(Collection arrays) { return concat(CollectionUtils.toArray(arrays, byte[].class)); } /** Truncates the given array to the given length. */ public static byte[] truncate(byte[] array, int newLength) { if (array.length < newLength) { return array; } else { byte[] truncated = new byte[newLength]; System.arraycopy(array, 0, truncated, 0, newLength); return truncated; } } /** * Creates a hex dump of the provided bytes. This is similar to output from hexdump tools and * primarily used for debugging. The output string will contain in each line 16 bytes of data first * printed as hex numbers and then as a string interpretation. Each line is also prefixed with an * offset. */ public static String hexDump(byte[] data) { return hexDump(data, 16); } /** * Creates a hex dump of the provided bytes. This is similar to output from hexdump tools and * primarily used for debugging. The output string will contain in each line width * bytes of data first printed as hex numbers and then as a string interpretation. Each line is also * prefixed with an offset. */ public static String hexDump(byte[] data, int width) { CCSMAssert.isTrue(width >= 1, "Width must be positive!"); StringBuilder builder = new StringBuilder(); for (int i = 0; i < data.length; i += width) { hexDumpAppendLine(data, i, Math.min(data.length, i + width), width, builder); } return builder.toString(); } /** * Reads an integer from the given byte array at the given offset. */ public static int getIntFromByteArray(byte[] data, int offset) { return (data[offset] & 255) << 24 | (data[offset + 1] & 255) << 16 | (data[offset + 2] & 255) << 8 | (data[offset + 3] & 255); } /** * Converts a list of integers to a space optimized byte array. (i.e. by just concatenating the 4 * byte integers next to each other). *

* Deserialize using {@link #deserializeIntegerList(byte[])}. */ public static byte[] serializeIntegerList(List integers) { byte[] data = new byte[integers.size() * Integer.BYTES]; for (int i = 0; i < integers.size(); i++) { putIntIntoByteArray(data, i * Integer.BYTES, integers.get(i)); } return data; } /** * Deserializes a byte array of integers, that was created using * {@link #serializeIntegerList(List)}. */ public static List deserializeIntegerList(byte[] data) { int listSize = data.length / Integer.BYTES; List result = new ArrayList<>(listSize); for (int i = 0; i < listSize; i++) { Integer value = getIntFromByteArray(data, Integer.BYTES * i); result.add(value); } return result; } /** * Read a 4-byte integer from a given input stream. */ private static int readIntFromStream(ByteArrayInputStream inputStream) throws IOException { byte[] intBytes = new byte[Integer.BYTES]; int elementSize = inputStream.read(intBytes); if (elementSize == 0) { return 0; } else if (elementSize != Integer.BYTES) { throw new IOException("Did expect an integer with 4 bytes"); } return byteArrayToInt(intBytes); } /** * Reads a long from the given byte array at the given offset. */ public static long getLongFromByteArray(byte[] data, int offset) { return (long) (data[offset] & 255) << 56 | (long) (data[offset + 1] & 255) << 48 | (long) (data[offset + 2] & 255) << 40 | (long) (data[offset + 3] & 255) << 32 | (long) (data[offset + 4] & 255) << 24 | (data[offset + 5] & 255) << 16 | (data[offset + 6] & 255) << 8 | (data[offset + 7] & 255); } /** * Reads a double from the given byte array at the given offset. */ public static double getDoubleFromByteArray(byte[] data, int offset) { return Double.longBitsToDouble(getLongFromByteArray(data, offset)); } /** * Puts the given integer into the byte array at position of the offset. */ public static void putIntIntoByteArray(byte[] data, int offset, int value) { data[offset] = (byte) ((value & 0xff000000) >> 24); data[offset + 1] = (byte) ((value & 0x00ff0000) >> 16); data[offset + 2] = (byte) ((value & 0x0000ff00) >> 8); data[offset + 3] = (byte) (value & 0x000000ff); } /** * Puts the given long into the byte array at position of the offset. */ public static void putLongIntoByteArray(byte[] data, int offset, long value) { data[offset] = (byte) (value >>> 56); data[offset + 1] = (byte) (value >>> 48); data[offset + 2] = (byte) (value >>> 40); data[offset + 3] = (byte) (value >>> 32); data[offset + 4] = (byte) (value >>> 24); data[offset + 5] = (byte) (value >>> 16); data[offset + 6] = (byte) (value >>> 8); data[offset + 7] = (byte) value; } /** * Puts the given double into the byte array at position of the offset. */ public static void putDoubleIntoByteArray(byte[] data, int offset, double value) { long longBits = Double.doubleToRawLongBits(value); putLongIntoByteArray(data, offset, longBits); } /** * Appends a single line to the hex dump for {@link #hexDump(byte[], int)}. The start is inclusive, * the end is exclusive. */ private static void hexDumpAppendLine(byte[] data, int startOffset, int endOffset, int width, StringBuilder builder) { builder.append(String.format("%06d: ", startOffset)); for (int i = startOffset; i < endOffset; ++i) { builder.append(String.format("%02x ", data[i])); } if (endOffset - startOffset < width) { builder.append(StringUtils.fillString((width - (endOffset - startOffset)) * 3, StringUtils.SPACE_CHAR)); } builder.append(StringUtils.SPACE_CHAR); for (int i = startOffset; i < endOffset; ++i) { boolean isInPrintableAsciiRange = (33 <= data[i] && data[i] <= 126); if (isInPrintableAsciiRange) { builder.append((char) data[i]); } else { builder.append('.'); } } builder.append(StringUtils.LINE_SEPARATOR); } /** * Returns whether the given bytes start with the * magic bytes that * mark a ZIP file. */ public static boolean startsWithZipMagicBytes(byte[] data) { return isPrefix(new byte[] { 0x50, 0x4b, 0x03, 0x04 }, data); } /** * Returns the first index in searchIn at or after the start index containing * searchFor (or -1 if not found). */ public static int indexOf(byte[] searchFor, byte[] searchIn, int startIndex) { return indexOf(searchFor, searchIn, startIndex, searchIn.length); } /** * Returns the first index in searchIn at or after the start index containing * searchFor (or -1 if not found). endIndex is the index of the first byte that is not * considered in the match (exclusive). */ public static int indexOf(byte[] searchFor, byte[] searchIn, int startIndex, int endIndex) { if (startIndex >= endIndex || startIndex + searchFor.length > endIndex) { return -1; } for (int i = startIndex; i <= endIndex - searchFor.length; ++i) { if (isPrefix(searchFor, searchIn, i)) { return i; } } return -1; } /** * Perform a split with no limit according to {@link #split(byte[], byte[], int)}. */ public static List split(byte[] bytes, byte[] separatorBytes) { return split(bytes, separatorBytes, Integer.MAX_VALUE); } /** * Splits the byte array at the separator bytes given maximum split amount of times. The result * {@link ArrayList} is never longer than the maximum split. * * @param bytes * The bytes to split. An empty list is returned if this is null. * @param separatorBytes * Non-null array of bytes to split at. Must have positive length. * @param maxSplits * the maximum number of splits to perform starting at the beginning of the bytes array. * An empty list is returned if this is 0 or negative. */ public static List split(byte[] bytes, byte[] separatorBytes, int maxSplits) { CCSMAssert.isNotNull(separatorBytes, "Separator bytes for byte array split can't be null."); CCSMAssert.isTrue(separatorBytes.length > 0, "Separator bytes array must have positive length."); List result = new ArrayList<>(); if (maxSplits <= 0 || bytes == null) { return result; } int start = 0; for (int i = 0; i < bytes.length; i++) { if (result.size() == maxSplits - 1) { break; } if (ByteArrayUtils.isPrefix(separatorBytes, bytes, i)) { result.add(Arrays.copyOfRange(bytes, start, i)); i += separatorBytes.length; start = i; } } result.add(Arrays.copyOfRange(bytes, start, bytes.length)); return result; } /** * If the data bytes start with the given prefix, a copy of {@code data} without the prefix is * returned. Otherwise, the original data array is returned unaltered. */ public static byte[] removePrefix(byte[] prefix, byte[] data) { if (isPrefix(prefix, data)) { return Arrays.copyOfRange(data, prefix.length, data.length); } return data; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy