* This class is immutable as long as the internal array is not changed from outside (which can't be assured, when
- * using using wrap()). It is possible to create a mutable version (see {@link MutableBytes}).
+ * using wrap()). It is possible to create a mutable version (see {@link MutableBytes}).
*
* Example:
*
- * Bytes b = Bytes.from(array);
+ * Bytes b = Bytes.from(array).mutable();
* b.not();
* System.out.println(b.encodeHex());
*
+ *
+ *
Comparable
+ * The implemented comparator treats the bytes as signed bytes. If you want to sort, treating each byte as unsigned,
+ * use {@link BytesTransformers#sortUnsigned()}.
*/
@SuppressWarnings("WeakerAccess")
public class Bytes implements Comparable, Serializable, Iterable {
+ private static final Bytes EMPTY = Bytes.wrap(new byte[0]);
+
/* FACTORY ***************************************************************************************************/
/**
@@ -80,6 +83,7 @@ public static Bytes allocate(int length) {
* @return new instance
*/
public static Bytes allocate(int length, byte defaultValue) {
+ if (length == 0) return empty();
byte[] array = new byte[length];
if (defaultValue != 0) {
Arrays.fill(array, defaultValue);
@@ -87,6 +91,15 @@ public static Bytes allocate(int length, byte defaultValue) {
return wrap(array);
}
+ /**
+ * Creates a Byte instance with an internal empty byte array. Same as calling {@link #allocate(int)} with 0.
+ *
+ * @return the empty instance (always the same reference
+ */
+ public static Bytes empty() {
+ return EMPTY;
+ }
+
/**
* Creates a new reference backed by the same byte array.
* Inherits all attributes (readonly, etc.)
@@ -95,7 +108,7 @@ public static Bytes allocate(int length, byte defaultValue) {
* @return new instance
*/
public static Bytes wrap(Bytes bytes) {
- return new Bytes(bytes.internalArray(), bytes.byteOrder);
+ return wrap(Objects.requireNonNull(bytes, "passed Byte instance must not be null").internalArray(), bytes.byteOrder);
}
/**
@@ -111,7 +124,7 @@ public static Bytes wrap(Bytes bytes) {
* @return new instance
*/
public static Bytes wrapNullSafe(byte[] array) {
- return wrap(array != null ? array : new byte[0]);
+ return array != null ? wrap(array) : empty();
}
/**
@@ -140,8 +153,7 @@ public static Bytes wrap(byte[] array) {
* @return new instance
*/
public static Bytes wrap(byte[] array, ByteOrder byteOrder) {
- Objects.requireNonNull(array, "passed array must not be null");
- return new Bytes(array, byteOrder);
+ return new Bytes(Objects.requireNonNull(array, "passed array must not be null"), byteOrder);
}
/**
@@ -152,8 +164,7 @@ public static Bytes wrap(byte[] array, ByteOrder byteOrder) {
* @return new instance
*/
public static Bytes from(byte[] byteArrayToCopy) {
- Objects.requireNonNull(byteArrayToCopy, "must at least pass a single byte");
- return wrap(Arrays.copyOf(byteArrayToCopy, byteArrayToCopy.length));
+ return wrap(Arrays.copyOf(Objects.requireNonNull(byteArrayToCopy, "must at least pass a single byte"), byteArrayToCopy.length));
}
/**
@@ -166,14 +177,14 @@ public static Bytes from(byte[] byteArrayToCopy) {
* @return new instance
*/
public static Bytes fromNullSafe(byte[] byteArrayToCopy) {
- return from(byteArrayToCopy != null ? byteArrayToCopy : new byte[0]);
+ return byteArrayToCopy != null ? from(byteArrayToCopy) : empty();
}
/**
* Creates a new instance from a slice of given array
*
* @param array to slice
- * @param offset stat position
+ * @param offset start position
* @param length length
* @return new instance
*/
@@ -191,7 +202,7 @@ public static Bytes from(byte[] array, int offset, int length) {
* @return new instance
*/
public static Bytes from(byte[]... moreArrays) {
- return wrap(Util.concat(moreArrays));
+ return wrap(Util.Byte.concat(moreArrays));
}
/**
@@ -217,7 +228,7 @@ public static Bytes from(Bytes... moreBytes) {
* @return new instance
*/
public static Bytes from(Collection bytesCollection) {
- return wrap(Util.toArray(bytesCollection));
+ return wrap(Util.Converter.toArray(bytesCollection));
}
/**
@@ -227,7 +238,7 @@ public static Bytes from(Collection bytesCollection) {
* @return new instance
*/
public static Bytes from(Byte[] boxedObjectArray) {
- return wrap(Util.toPrimitiveArray(boxedObjectArray));
+ return wrap(Util.Converter.toPrimitiveArray(boxedObjectArray));
}
/**
@@ -249,7 +260,7 @@ public static Bytes from(byte singleByte) {
* @return new instance
*/
public static Bytes from(byte firstByte, byte... moreBytes) {
- return wrap(Util.concatVararg(firstByte, moreBytes));
+ return wrap(Util.Byte.concatVararg(firstByte, moreBytes));
}
/**
@@ -285,6 +296,16 @@ public static Bytes from(short short2Byte) {
return wrap(ByteBuffer.allocate(2).putShort(short2Byte).array());
}
+ /**
+ * Creates a new instance from given 2 byte short array.
+ *
+ * @param shortArray to create from
+ * @return new instance
+ */
+ public static Bytes from(short... shortArray) {
+ return wrap(Util.Converter.toByteArray(Objects.requireNonNull(shortArray, "must provide at least a single short")));
+ }
+
/**
* Creates a new instance from given 4 byte integer.
*
@@ -302,8 +323,7 @@ public static Bytes from(int integer4byte) {
* @return new instance
*/
public static Bytes from(int... intArray) {
- Objects.requireNonNull(intArray, "must provide at least a single int");
- return wrap(Util.toByteArray(intArray));
+ return wrap(Util.Converter.toByteArray(Objects.requireNonNull(intArray, "must provide at least a single int")));
}
/**
@@ -323,8 +343,7 @@ public static Bytes from(long long8byte) {
* @return new instance
*/
public static Bytes from(long... longArray) {
- Objects.requireNonNull(longArray, "must provide at least a single long");
- return wrap(Util.toByteArray(longArray));
+ return wrap(Util.Converter.toByteArray(Objects.requireNonNull(longArray, "must provide at least a single long")));
}
/**
@@ -337,6 +356,16 @@ public static Bytes from(float float4byte) {
return wrap(ByteBuffer.allocate(4).putFloat(float4byte).array());
}
+ /**
+ * Creates a new instance from given float array.
+ *
+ * @param floatArray to create from
+ * @return new instance
+ */
+ public static Bytes from(float... floatArray) {
+ return wrap(Util.Converter.toByteArray(Objects.requireNonNull(floatArray, "must provide at least a single float")));
+ }
+
/**
* Creates a new instance from given 8 byte floating point number (double).
*
@@ -348,14 +377,46 @@ public static Bytes from(double double8Byte) {
}
/**
- * Creates a new instance from given ByteBuffer.
+ * Creates a new instance from given double array.
+ *
+ * @param doubleArray to create from
+ * @return new instance
+ */
+ public static Bytes from(double... doubleArray) {
+ return wrap(Util.Converter.toByteArray(Objects.requireNonNull(doubleArray, "must provide at least a single double")));
+ }
+
+ /**
+ * Creates a new instance from given {@link ByteBuffer}.
* Will use the same backing byte array and honour the buffer's byte order.
*
- * @param buffer to get the byte array from
+ * @param buffer to get the byte array from (must not be null)
* @return new instance
*/
public static Bytes from(ByteBuffer buffer) {
- return wrap(buffer.array(), buffer.order());
+ return wrap(Objects.requireNonNull(buffer, "provided byte buffer must not be null").array(), buffer.order());
+ }
+
+ /**
+ * Creates a new instance from given {@link CharBuffer}.
+ * Will ignore buffer's byte order and use {@link ByteOrder#BIG_ENDIAN}
+ *
+ * @param buffer to get the char array from (must not be null)
+ * @return new instance
+ */
+ public static Bytes from(CharBuffer buffer) {
+ return from(Objects.requireNonNull(buffer, "provided char buffer must not be null").array());
+ }
+
+ /**
+ * Creates a new instance from given {@link IntBuffer}.
+ * Will ignore buffer's byte order and use {@link ByteOrder#BIG_ENDIAN}
+ *
+ * @param buffer to get the int array from (must not be null)
+ * @return new instance
+ */
+ public static Bytes from(IntBuffer buffer) {
+ return from(Objects.requireNonNull(buffer, "provided int buffer must not be null").array());
}
/**
@@ -369,7 +430,6 @@ public static Bytes from(BitSet set) {
}
/**
- * /**
* Creates a new instance from given {@link BigInteger}.
*
* @param bigInteger to get the byte array from
@@ -380,13 +440,25 @@ public static Bytes from(BigInteger bigInteger) {
}
/**
- * Reads given input stream and creates a new instance from read data
+ * Reads given whole input stream and creates a new instance from read data
*
* @param stream to read from
* @return new instance
*/
public static Bytes from(InputStream stream) {
- return wrap(Util.readFromStream(stream));
+ return wrap(Util.File.readFromStream(stream, -1));
+ }
+
+ /**
+ * Reads given input stream up to maxLength and creates a new instance from read data.
+ * Read maxLength is never longer than stream size (i.e. maxLength is only limiting, not assuring maxLength)
+ *
+ * @param stream to read from
+ * @param maxLength read to this maxLength or end of stream
+ * @return new instance
+ */
+ public static Bytes from(InputStream stream, int maxLength) {
+ return wrap(Util.File.readFromStream(stream, maxLength));
}
/**
@@ -397,7 +469,7 @@ public static Bytes from(InputStream stream) {
* @return new instance
*/
public static Bytes from(DataInput dataInput, int length) {
- return wrap(Util.readFromDataInput(dataInput, length));
+ return wrap(Util.File.readFromDataInput(dataInput, length));
}
/**
@@ -410,7 +482,22 @@ public static Bytes from(DataInput dataInput, int length) {
* @throws IllegalStateException if file could not be read
*/
public static Bytes from(File file) {
- return wrap(Util.readFromFile(file));
+ return wrap(Util.File.readFromFile(file));
+ }
+
+ /**
+ * Reads given file and returns the byte content. Be aware that the whole defined file content will be loaded to
+ * memory, so be careful what to read in. This uses {@link java.io.RandomAccessFile} under the hood.
+ *
+ * @param file to read from
+ * @param offset byte offset from zero position of the file
+ * @param length to read from offset
+ * @return new instance
+ * @throws IllegalArgumentException if file does not exist
+ * @throws IllegalStateException if file could not be read
+ */
+ public static Bytes from(File file, int offset, int length) {
+ return wrap(Util.File.readFromFile(file, offset, length));
}
/**
@@ -442,9 +529,7 @@ public static Bytes from(CharSequence utf8String, Normalizer.Form form) {
* @return new instance
*/
public static Bytes from(CharSequence string, Charset charset) {
- Objects.requireNonNull(string, "provided string must not be null");
- Objects.requireNonNull(charset, "provided charset must not be null");
- return wrap(string.toString().getBytes(charset));
+ return wrap(Objects.requireNonNull(string, "provided string must not be null").toString().getBytes(Objects.requireNonNull(charset, "provided charset must not be null")));
}
/**
@@ -454,7 +539,42 @@ public static Bytes from(CharSequence string, Charset charset) {
* @return new instance
*/
public static Bytes from(char[] charArray) {
- return from(CharBuffer.wrap(charArray).toString(), StandardCharsets.UTF_8);
+ return from(charArray, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Creates a new instance from given char array. The array will be handles like an encoded string
+ *
+ * @param charArray to get the internal byte array from
+ * @param charset charset to be used to decode the char array
+ * @return new instance
+ */
+ public static Bytes from(char[] charArray, Charset charset) {
+ return from(charArray, charset, 0, charArray.length);
+ }
+
+ /**
+ * Creates a new instance from given char array with given range. The array will be handles like an encoded string
+ *
+ * @param charArray to get the internal byte array from
+ * @param charset charset to be used to decode the char array
+ * @param offset start position (from given char array not encoded byte array out)
+ * @param length length in relation to offset (from given char array not encoded byte array out)
+ * @return new instance
+ */
+ public static Bytes from(char[] charArray, Charset charset, int offset, int length) {
+ return from(Util.Converter.charToByteArray(charArray, charset, offset, length));
+ }
+
+ /**
+ * Convert UUID to a newly generated 16 byte long array representation. Puts the 8 byte most significant bits and
+ * 8 byte the least significant bits into a byte array.
+ *
+ * @param uuid to convert to array
+ * @return new instance
+ */
+ public static Bytes from(UUID uuid) {
+ return wrap(Util.Converter.toBytesFromUUID(Objects.requireNonNull(uuid)).array());
}
/**
@@ -463,8 +583,8 @@ public static Bytes from(char[] charArray) {
* @param binaryString the encoded string
* @return decoded instance
*/
- public static Bytes parseBinary(String binaryString) {
- return parse(binaryString, new BinaryToTextEncoding.BaseRadix(2));
+ public static Bytes parseBinary(CharSequence binaryString) {
+ return parseRadix(binaryString, 2);
}
/**
@@ -473,8 +593,8 @@ public static Bytes parseBinary(String binaryString) {
* @param octalString the encoded string
* @return decoded instance
*/
- public static Bytes parseOctal(String octalString) {
- return parse(octalString, new BinaryToTextEncoding.BaseRadix(8));
+ public static Bytes parseOctal(CharSequence octalString) {
+ return parseRadix(octalString, 8);
}
/**
@@ -483,38 +603,77 @@ public static Bytes parseOctal(String octalString) {
* @param decString the encoded string
* @return decoded instance
*/
- public static Bytes parseDec(String decString) {
- return parse(decString, new BinaryToTextEncoding.BaseRadix(10));
+ public static Bytes parseDec(CharSequence decString) {
+ return parseRadix(decString, 10);
+ }
+
+ /**
+ * Encodes with given radix string representation (e.g. radix 16 would be hex).
+ * See also {@link BigInteger#toString(int)}.
+ *
+ * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation
+ * tries to preserve the leading zeros, to keep the in/output byte length size the same, but use at your own risk!
+ *
+ * @param radixNumberString the encoded string
+ * @param radix radix of the String representation (supported are 2-36)
+ * @return decoded instance
+ */
+ public static Bytes parseRadix(CharSequence radixNumberString, int radix) {
+ return parse(radixNumberString, new BinaryToTextEncoding.BaseRadixNumber(radix));
}
/**
- * Parsing of base16/HEX encoded byte arrays. Will accept upper- and lowercase variant and ignores
- * possible "0x" prefix.
+ * Parsing of base16/HEX encoded byte arrays. This is by design a very flexible decoder accepting the following cases:
+ *
+ *
+ *
Upper- and lowercase a-f (also mixed case)
+ *
Prefix with 0x which will be ignored
+ *
Even and odd number of string length with auto zero padding (i.e. 'E3F' is same as '0E3F')
+ *
*
* @param hexString the encoded string
* @return decoded instance
+ * @throws IllegalArgumentException if string contains something else than [0-9a-fA-F]
*/
- public static Bytes parseHex(String hexString) {
+ public static Bytes parseHex(CharSequence hexString) {
return parse(hexString, new BinaryToTextEncoding.Hex());
}
+ /**
+ * Parsing of base32/RFC 4648 encoded byte arrays.
+ *
+ * Uses the RFC 4648 non-hex alphabet, see Base32 alphabet.
+ *
+ * @param base32Rfc4648String the encoded string
+ * @return decoded instance
+ */
+ public static Bytes parseBase32(CharSequence base32Rfc4648String) {
+ return parse(base32Rfc4648String, new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING));
+ }
+
/**
* Parsing of base36 encoded byte arrays.
+ *
+ * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation
+ * tries to preserve the leading zeros, to keep the in/output byte length size the same.
*
* @param base36String the encoded string
* @return decoded instance
+ * @deprecated use {@link #parseRadix(CharSequence, int)} with 36 instead; will be removed in v1.0+
*/
- public static Bytes parseBase36(String base36String) {
- return parse(base36String, new BinaryToTextEncoding.BaseRadix(36));
+ @Deprecated
+ public static Bytes parseBase36(CharSequence base36String) {
+ return parse(base36String, new BinaryToTextEncoding.BaseRadixNumber(36));
}
/**
* Parsing of base64 encoded byte arrays.
+ * Supporting RFC 4648 normal and url safe encoding, with or without padding.
*
* @param base64String the encoded string
* @return decoded instance
*/
- public static Bytes parseBase64(String base64String) {
+ public static Bytes parseBase64(CharSequence base64String) {
return parse(base64String, new BinaryToTextEncoding.Base64Encoding());
}
@@ -525,11 +684,8 @@ public static Bytes parseBase64(String base64String) {
* @param decoder the decoder used to decode the string
* @return decoded instance
*/
- public static Bytes parse(String encoded, BinaryToTextEncoding.Decoder decoder) {
- Objects.requireNonNull(encoded, "encoded data must not be null");
- Objects.requireNonNull(decoder, "passed decoder instance must no be null");
-
- return wrap(decoder.decode(encoded));
+ public static Bytes parse(CharSequence encoded, BinaryToTextEncoding.Decoder decoder) {
+ return wrap(Objects.requireNonNull(decoder, "passed decoder instance must no be null").decode(Objects.requireNonNull(encoded, "encoded data must not be null")));
}
/**
@@ -542,6 +698,35 @@ public static Bytes random(int length) {
return random(length, new SecureRandom());
}
+ /**
+ * A new instance with pseudo random bytes using an unsecure random number generator.
+ * This may be used in e.g. tests. In production code use {@link #random(int)} per default.
+ *
+ * ONLY USE IN NON-SECURITY RELEVANT CONTEXT!
+ *
+ * @param length desired array length
+ * @return random instance
+ */
+ public static Bytes unsecureRandom(int length) {
+ return random(length, new Random());
+ }
+
+ /**
+ * A new instance with pseudo random bytes using an unsecure random number generator.
+ * This may be used in e.g. tests to create predictable numbers.
+ *
+ * In production code use {@link #random(int)} per default.
+ *
+ * ONLY USE IN NON-SECURITY RELEVANT CONTEXT!
+ *
+ * @param length desired array length
+ * @param seed used to seed random number generator - using same seed will generate same numbers
+ * @return random instance
+ */
+ public static Bytes unsecureRandom(int length, long seed) {
+ return random(length, new Random(seed));
+ }
+
/**
* A new instance with random bytes.
*
@@ -580,7 +765,7 @@ public static Bytes random(int length, Random random) {
/* TRANSFORMER **********************************************************************************************/
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end).
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end).
*
* This will create a new byte array internally, so it is not suitable to use as extensive builder pattern -
* use {@link ByteBuffer} or {@link java.io.ByteArrayOutputStream} for that.
@@ -593,7 +778,7 @@ public Bytes append(Bytes bytes) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param singleByte to append
* @return appended instance
@@ -603,7 +788,7 @@ public Bytes append(byte singleByte) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param char2Bytes to append
* @return appended instance
@@ -613,7 +798,7 @@ public Bytes append(char char2Bytes) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param short2Bytes to append
* @return appended instance
@@ -623,7 +808,7 @@ public Bytes append(short short2Bytes) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param integer4Bytes to append
* @return appended instance
@@ -633,7 +818,7 @@ public Bytes append(int integer4Bytes) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param long8Bytes to append
* @return appended instance
@@ -643,7 +828,19 @@ public Bytes append(long long8Bytes) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end).
+ * You may use this to append multiple byte arrays without the need for chaining the {@link #append(byte[])} call
+ * and therefore generating intermediate copies of the byte array, making this approach use less memory.
+ *
+ * @param arrays to append
+ * @return appended instance
+ */
+ public Bytes append(byte[]... arrays) {
+ return append(Bytes.from(arrays));
+ }
+
+ /**
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* @param secondArray to append
* @return appended instance
@@ -653,7 +850,7 @@ public Bytes append(byte[] secondArray) {
}
/**
- * Creates a new instance with the current array appended to the provided data (ie. append at the end)
+ * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
*
* If given array is null, the nothing will be appended.
*
@@ -682,9 +879,7 @@ public Bytes append(CharSequence stringUtf8) {
* @return appended instance
*/
public Bytes append(CharSequence string, Charset charset) {
- Objects.requireNonNull(charset);
- Objects.requireNonNull(string);
- return transform(new BytesTransformer.ConcatTransformer(string.toString().getBytes(charset)));
+ return transform(new BytesTransformer.ConcatTransformer(Objects.requireNonNull(string).toString().getBytes(Objects.requireNonNull(charset))));
}
/**
@@ -744,7 +939,7 @@ public Bytes and(byte[] secondArray) {
* @see Bitwise operators: OR
*/
public Bytes or(Bytes bytes) {
- return and(bytes.internalArray());
+ return or(bytes.internalArray());
}
/**
@@ -781,7 +976,11 @@ public Bytes not() {
* @see Bit shifts
*/
public Bytes leftShift(int shiftCount) {
- return transform(new BytesTransformer.ShiftTransformer(shiftCount, BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT));
+ return transform(new BytesTransformer.ShiftTransformer(
+ shiftCount,
+ BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT,
+ byteOrder
+ ));
}
/**
@@ -796,11 +995,15 @@ public Bytes leftShift(int shiftCount) {
* @see Bit shifts
*/
public Bytes rightShift(int shiftCount) {
- return transform(new BytesTransformer.ShiftTransformer(shiftCount, BytesTransformer.ShiftTransformer.Type.RIGHT_SHIFT));
+ return transform(new BytesTransformer.ShiftTransformer(
+ shiftCount,
+ BytesTransformer.ShiftTransformer.Type.RIGHT_SHIFT,
+ byteOrder
+ ));
}
/**
- * Returns a Byte whose value is equivalent to this Byte with the designated bit set to newBitValue. Bits start to count from the LSB (ie. Bytes.from(0).switchBit(0,true) == 1)
+ * Returns a Byte whose value is equivalent to this Byte with the designated bit set to newBitValue. Bits start to count from the LSB (i.e. Bytes.from(0).switchBit(0,true) == 1)
*
* @param bitPosition not to confuse with byte position
* @param newBitValue if true set to 1, 0 otherwise
@@ -859,7 +1062,7 @@ public Bytes reverse() {
* copy but not the original, the copy will contain {@code (byte)0}.
*
* Resize from LSB or length, so an array [0,1,2,3] resized to 3 will result in [1,2,3] or resized to 5 [0,0,1,2,3].
- * So when a 8 byte value resized to 4 byte will result in the same 32 bit integer value
+ * So when an 8 byte value resized to 4 byte will result in the same 32-bit integer value
*
* @param newByteLength the length of the copy to be returned
* @return a copy with the desired size or "this" instance if newByteLength == current length
@@ -891,6 +1094,36 @@ public Bytes resize(int newByteLength, BytesTransformer.ResizeTransformer.Mode m
return transform(new BytesTransformer.ResizeTransformer(newByteLength, mode));
}
+ /**
+ * Calculates md5 on the underlying byte array and returns a byte instance containing the hash.
+ * This hash algorithm SHOULD be supported by every JVM implementation (see
+ * Javadoc for MessageDigest)
+ *
+ * Do not use this algorithm in security relevant applications.
+ *
+ * @return md5 (16 bytes) hash of internal byte array
+ * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
+ * @see MD5
+ */
+ public Bytes hashMd5() {
+ return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_MD5);
+ }
+
+ /**
+ * Calculates sha1 on the underlying byte array and returns a byte instance containing the hash.
+ * This hash algorithm SHOULD be supported by every JVM implementation (see
+ * Javadoc for MessageDigest)
+ *
+ * Do not use this algorithm in security relevant applications.
+ *
+ * @return sha1 (20 bytes) hash of internal byte array
+ * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
+ * @see Secure Hash Algorithm 1
+ */
+ public Bytes hashSha1() {
+ return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_SHA_1);
+ }
+
/**
* Calculates sha256 on the underlying byte array and returns a byte instance containing the hash.
*
@@ -917,10 +1150,10 @@ public Bytes hash(String algorithm) {
/**
* Generic transformation of this instance.
*
- * This transformation might be done in-place (ie. without copying the internal array and overwriting its old state),
+ * This transformation might be done in-place (i.e. without copying the internal array and overwriting its old state),
* or on a copy of the internal data, depending on the type (e.g. {@link MutableBytes}) and if the operation can be done
- * in-place. Therefore the caller has to ensure that certain side-effects, which occur due to the changing of the internal
- * data, do not create bugs in his/her code. Usually immutability is prefered, but when handling many or big byte arrays,
+ * in-place. Therefore, the caller has to ensure that certain side effects, which occur due to the changing of the internal
+ * data, do not create bugs in his/her code. Usually immutability is preferred, but when handling many or big byte arrays,
* mutability enables drastically better performance.
*
* @param transformer used to transform this instance
@@ -948,8 +1181,7 @@ public boolean validateNotOnlyZeros() {
* @return true if all validators return true
*/
public boolean validate(BytesValidator... bytesValidators) {
- Objects.requireNonNull(bytesValidators);
- return BytesValidators.and(bytesValidators).validate(internalArray());
+ return BytesValidators.and(Objects.requireNonNull(bytesValidators)).validate(internalArray());
}
/* ATTRIBUTES ************************************************************************************************/
@@ -966,7 +1198,7 @@ public int length() {
/**
* The bit length of the underlying byte array.
*
- * @return bit length
+ * @return the bit length
*/
public int lengthBit() {
return length() * 8;
@@ -994,7 +1226,7 @@ public ByteOrder byteOrder() {
/**
* Checks if instance is mutable
*
- * @return true if mutable, ie. transformers will change internal array
+ * @return true if mutable, i.e. transformers will change internal array
*/
public boolean isMutable() {
return false;
@@ -1021,14 +1253,41 @@ public boolean contains(byte target) {
/**
* Returns the index of the first appearance of the value {@code target} in
- * {@code array}.
+ * {@code array}. Same as calling {@link #indexOf(byte, int)} with fromIndex '0'.
*
* @param target a primitive {@code byte} value
* @return the least index {@code i} for which {@code array[i] == target}, or
* {@code -1} if no such index exists.
*/
public int indexOf(byte target) {
- return Util.indexOf(internalArray(), target, 0, length());
+ return indexOf(target, 0);
+ }
+
+ /**
+ * Returns the index of the first appearance of the value {@code target} in
+ * {@code array} from given start index 'fromIndex'.
+ *
+ * @param target a primitive {@code byte} value
+ * @param fromIndex search from this index
+ * @return the least index {@code i} for which {@code array[i] == target}, or
+ * {@code -1} if no such index exists or fromIndex is gt target length.
+ */
+ public int indexOf(byte target, int fromIndex) {
+ return indexOf(new byte[]{target}, fromIndex);
+ }
+
+ /**
+ * Returns the index of the first appearance of the value {@code target} in
+ * {@code array} from given start index 'fromIndex' to given end index 'toIndex'.
+ *
+ * @param target a primitive {@code byte} value
+ * @param fromIndex search from this index
+ * @param toIndex search to this index
+ * @return the least index {@code i} for which {@code array[i] == target}, or
+ * {@code -1} if no such index exists or fromIndex is gt target length.
+ */
+ public int indexOf(byte target, int fromIndex, int toIndex) {
+ return indexOf(new byte[]{target}, fromIndex, toIndex);
}
/**
@@ -1044,7 +1303,55 @@ public int indexOf(byte target) {
* {@code -1} if no such index exists.
*/
public int indexOf(byte[] subArray) {
- return Util.indexOf(internalArray(), subArray);
+ return indexOf(subArray, 0);
+ }
+
+ /**
+ * Returns the start position of the first occurrence of the specified {@code
+ * target} within {@code array} from given start index 'fromIndex', or {@code -1}
+ * if there is no such occurrence.
+ *
+ * More formally, returns the lowest index {@code i} such that {@code
+ * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly
+ * the same elements as {@code target}.
+ *
+ * @param subArray the array to search for as a sub-sequence of {@code array}
+ * @param fromIndex search from this index
+ * @return the least index {@code i} for which {@code array[i] == target}, or
+ * {@code -1} if no such index exists.
+ */
+ public int indexOf(byte[] subArray, int fromIndex) {
+ return Util.Byte.indexOf(internalArray(), subArray, fromIndex, length());
+ }
+
+ /**
+ * Returns the start position of the first occurrence of the specified {@code
+ * target} within {@code array} from given start index 'fromIndex' to given end
+ * index 'toIndex', or {@code -1} if there is no such occurrence.
+ *
+ * More formally, returns the lowest index {@code i} such that {@code
+ * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly
+ * the same elements as {@code target}.
+ *
+ * @param subArray the array to search for as a sub-sequence of {@code array}
+ * @param fromIndex search from this index
+ * @param toIndex search to this index
+ * @return the least index {@code i} for which {@code array[i] == target}, or
+ * {@code -1} if no such index exists.
+ */
+ public int indexOf(byte[] subArray, int fromIndex, int toIndex) {
+ return Util.Byte.indexOf(internalArray(), subArray, fromIndex, toIndex);
+ }
+
+ /**
+ * Checks if the given sub array is equal to the start of given array. That is, sub array must be gt or eq
+ * to the length of the internal array and internal[i] == subArray[i] for i=0..subArray.length-1
+ *
+ * @param subArray to check against the start of the internal array
+ * @return true if the start of the internal array is eq to given sub array
+ */
+ public boolean startsWith(byte[] subArray) {
+ return Util.Byte.indexOf(internalArray(), subArray, 0, 1) == 0;
}
/**
@@ -1056,7 +1363,19 @@ public int indexOf(byte[] subArray) {
* or {@code -1} if no such index exists.
*/
public int lastIndexOf(byte target) {
- return Util.lastIndexOf(internalArray(), target, 0, length());
+ return Util.Byte.lastIndexOf(internalArray(), target, 0, length());
+ }
+
+ /**
+ * Checks if the given sub array is equal to the end of given array. That is, sub array must be gt or eq
+ * to the length of the internal array and internal[i] == subArray[i] for i=subArray.length...internal.length
+ *
+ * @param subArray to check against the end of the internal array
+ * @return true if the end of the internal array is eq to given sub array
+ */
+ public boolean endsWith(byte[] subArray) {
+ int startIndex = length() - subArray.length;
+ return startIndex >= 0 && Util.Byte.indexOf(internalArray(), subArray, startIndex, startIndex + 1) == startIndex;
}
/**
@@ -1064,12 +1383,16 @@ public int lastIndexOf(byte target) {
* 1000 0000 has bitAt(0) == false and bitAt(7) == true.
*
* @param bitIndex the index of the {@code bit} value.
- * @return true if bit at given index is set, false otherwise
+ * @return true if the bit at given index is set, false otherwise
* @throws IndexOutOfBoundsException if the {@code bitIndex} argument is negative or not less than the length of this array in bits.
*/
public boolean bitAt(int bitIndex) {
- Util.checkIndexBounds(lengthBit(), bitIndex, 1, "bit");
- return ((byteAt(length() - 1 - (bitIndex / 8)) >>> bitIndex % 8) & 1) != 0;
+ Util.Validation.checkIndexBounds(lengthBit(), bitIndex, 1, "bit");
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ return ((byteAt(length() - 1 - (bitIndex / 8)) >>> bitIndex % 8) & 1) != 0;
+ } else {
+ return ((byteAt(bitIndex / 8) >>> bitIndex % 8) & 1) != 0;
+ }
}
/**
@@ -1082,7 +1405,7 @@ public boolean bitAt(int bitIndex) {
* @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than the length of this array.
*/
public byte byteAt(int index) {
- Util.checkIndexBounds(length(), index, 1, "byte");
+ Util.Validation.checkIndexBounds(length(), index, 1, "byte");
return internalArray()[index];
}
@@ -1096,7 +1419,7 @@ public byte byteAt(int index) {
* @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than the length of this array.
*/
public int unsignedByteAt(int index) {
- Util.checkIndexBounds(length(), index, 1, "unsigned byte");
+ Util.Validation.checkIndexBounds(length(), index, 1, "unsigned byte");
return 0xff & internalArray()[index];
}
@@ -1109,8 +1432,8 @@ public int unsignedByteAt(int index) {
* @throws IndexOutOfBoundsException if the {@code index} argument is negative or length is greater than index - 2
*/
public char charAt(int index) {
- Util.checkIndexBounds(length(), index, 2, "char");
- return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getChar();
+ Util.Validation.checkIndexBounds(length(), index, 2, "char");
+ return internalBuffer().position(index).getChar();
}
/**
@@ -1122,8 +1445,8 @@ public char charAt(int index) {
* @throws IndexOutOfBoundsException if the {@code index} argument is negative or length is greater than index - 2
*/
public short shortAt(int index) {
- Util.checkIndexBounds(length(), index, 2, "short");
- return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getShort();
+ Util.Validation.checkIndexBounds(length(), index, 2, "short");
+ return internalBuffer().position(index).getShort();
}
/**
@@ -1135,8 +1458,8 @@ public short shortAt(int index) {
* @throws IndexOutOfBoundsException if the {@code int} argument is negative or length is greater than index - 4
*/
public int intAt(int index) {
- Util.checkIndexBounds(length(), index, 4, "int");
- return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getInt();
+ Util.Validation.checkIndexBounds(length(), index, 4, "int");
+ return internalBuffer().position(index).getInt();
}
/**
@@ -1148,8 +1471,8 @@ public int intAt(int index) {
* @throws IndexOutOfBoundsException if the {@code long} argument is negative or length is greater than index - 8
*/
public long longAt(int index) {
- Util.checkIndexBounds(length(), index, 8, "long");
- return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getLong();
+ Util.Validation.checkIndexBounds(length(), index, 8, "long");
+ return internalBuffer().position(index).getLong();
}
/**
@@ -1160,13 +1483,25 @@ public long longAt(int index) {
* @return the count of given target in the byte array
*/
public int count(byte target) {
- int count = 0;
- for (byte b : internalArray()) {
- if (b == target) {
- count++;
- }
- }
- return count;
+ return Util.Byte.countByte(internalArray(), target);
+ }
+
+ /**
+ * Traverses the internal byte array counts the occurrences of given pattern array.
+ * This has a time complexity of O(n).
+ *
+ * Example:
+ *
+ *
Internal Array: [0, 1, 2, 0, 1, 0]
+ *
Pattern Array: [0, 1]
+ *
Count: 2
+ *
+ *
+ * @param pattern byte array to count
+ * @return the count of given target in the byte array
+ */
+ public int count(byte[] pattern) {
+ return Util.Byte.countByteArray(internalArray(), pattern);
}
/**
@@ -1176,13 +1511,13 @@ public int count(byte target) {
* variables. Specifically, assuming for simplicity that each of the microscopic configurations is equally probable,
* the entropy of the system is the natural logarithm of that number of configurations, multiplied by the Boltzmann constant kB.
*
- * This implementation requires O(n) time and space complexity.
+ * This implementation requires O(n) time and O(1) space complexity.
*
* @return entropy value; higher is more entropy (simply: more different values)
* @see Entropy
*/
public double entropy() {
- return new Util.Entropy<>(toList()).entropy();
+ return Util.Byte.entropy(internalArray());
}
/* CONVERTERS POSSIBLY REUSING THE INTERNAL ARRAY ***************************************************************/
@@ -1199,7 +1534,7 @@ public Bytes duplicate() {
/**
* Set the byte order or endianness of this instance. Default in Java is {@link ByteOrder#BIG_ENDIAN}.
*
- * This option is important for all encoding and conversation methods.
+ * This option is important for all encoding and conversion methods.
*
* @param byteOrder new byteOrder
* @return a new instance with the same underlying array and new order, or "this" if order is the same
@@ -1214,7 +1549,7 @@ public Bytes byteOrder(ByteOrder byteOrder) {
/**
* Returns a new read-only byte instance. Read-only means, that there is no direct access to the underlying byte
- * array and all transformers will create a copy (ie. immutable)
+ * array and all transformers will create a copy.
*
* @return a new instance if not already readonly, or "this" otherwise
*/
@@ -1270,7 +1605,7 @@ public InputStream inputStream() {
/**
* The reference of te internal byte-array. This call requires no conversation or additional memory allocation.
*
- * Modifications to this bytes's content will cause the returned
+ * Modifications to these byte's content will cause the returned
* array's content to be modified, and vice versa.
*
* @return the direct reference of the internal byte array
@@ -1296,7 +1631,7 @@ byte[] internalArray() {
* @see Binary number
*/
public String encodeBinary() {
- return encode(new BinaryToTextEncoding.BaseRadix(2));
+ return encodeRadix(2);
}
/**
@@ -1308,7 +1643,7 @@ public String encodeBinary() {
* @see Octal
*/
public String encodeOctal() {
- return encode(new BinaryToTextEncoding.BaseRadix(8));
+ return encodeRadix(8);
}
/**
@@ -1320,7 +1655,27 @@ public String encodeOctal() {
* @see Decimal
*/
public String encodeDec() {
- return encode(new BinaryToTextEncoding.BaseRadix(10));
+ return encodeRadix(10);
+ }
+
+ /**
+ * Encodes the internal array in given radix representation (e.g. 2 = binary, 10 = decimal, 16 = hex).
+ *
+ * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation
+ * tries to preserve the leading zeros, to keep the in/output byte length size the same. To preserve the length padding
+ * would be required, but is not supported in this implementation.
+ *
+ * But still full disclaimer:
+ *
+ * This is NOT recommended for data encoding, only for number encoding
+ *
+ * See Radix Economy and {@link BigInteger#toString(int)}.
+ *
+ * @param radix of the String representation (supported are 2-36)
+ * @return string in given radix representation
+ */
+ public String encodeRadix(int radix) {
+ return encode(new BinaryToTextEncoding.BaseRadixNumber(radix));
}
/**
@@ -1348,6 +1703,21 @@ public String encodeHex(boolean upperCase) {
}
/**
+ * Base32 RFC4648 string representation of the internal byte array (not Base32 hex alphabet extension)
+ *
+ * Example: MZXW6YQ=
+ *
+ * See RFC 4648
+ *
+ * @return base32 string
+ */
+ public String encodeBase32() {
+ return encode(new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING));
+ }
+
+ /**
+ * DO NOT USE AS DATA ENCODING, ONLY FOR NUMBERS!
+ *
* Base36 (aka Hexatrigesimal) representation. The choice of 36 is convenient in that the digits can be
* represented using the Arabic numerals 0–9 and the Latin letters A–Z. This encoding has a space efficiency of 64.6%.
*
@@ -1355,21 +1725,55 @@ public String encodeHex(boolean upperCase) {
*
* @return base36 string
* @see Base36
+ * @deprecated use {@link #encodeRadix(int)} instead; will be removed in v1.0+
*/
+ @Deprecated
public String encodeBase36() {
- return encode(new BinaryToTextEncoding.BaseRadix(36));
+ return encodeRadix(36);
}
/**
* Base64 representation with padding. This is *NOT* the url safe variation. This encoding has a space efficiency of 75%.
*
* Example: SpT9/x6v7Q==
*
* @return base64 string
* @see Base64
*/
public String encodeBase64() {
- return encode(new BinaryToTextEncoding.Base64Encoding());
+ return encodeBase64(false, true);
+ }
+
+ /**
+ * Base64 representation with padding. This is the url safe variation substitution '+' and '/' with '-' and '_'
+ * respectively. This encoding has a space efficiency of 75%.
+ *
+ * Example: SpT9_x6v7Q==
+ *
+ * @return base64 url safe string
+ * @see Base64
+ */
+ public String encodeBase64Url() {
+ return encodeBase64(true, true);
+ }
+
+ /**
+ * Base64 representation with either padding or without and with or without URL and filename safe alphabet.
+ * This encoding is RFC 4648 compatible.
+ *
+ * Example: SpT9/x6v7Q==
+ *
+ * @param urlSafe if true will substitute '+' and '/' with '-' and '_'
+ * @param withPadding if true will add padding the next full byte with '='
+ * @return base64 url safe string
+ * @see Base64
+ */
+ public String encodeBase64(boolean urlSafe, boolean withPadding) {
+ return encode(new BinaryToTextEncoding.Base64Encoding(urlSafe, withPadding));
}
/**
@@ -1389,8 +1793,31 @@ public String encodeUtf8() {
* @return encoded string
*/
public String encodeCharset(Charset charset) {
- Objects.requireNonNull(charset, "given charset must not be null");
- return new String(internalArray(), charset);
+ return new String(internalArray(), Objects.requireNonNull(charset, "given charset must not be null"));
+ }
+
+ /**
+ * UTF-8 representation of this byte array as byte array
+ *
+ * Similar to encodeUtf8().getBytes(StandardCharsets.UTF_8).
+ *
+ * @return utf-8 encoded byte array
+ * @see UTF-8
+ */
+ public byte[] encodeUtf8ToBytes() {
+ return encodeCharsetToBytes(StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Byte array representation with given charset encoding.
+ *
+ * Similar to encodeCharset(charset).getBytes(charset).
+ *
+ * @param charset the charset the return will be encoded
+ * @return encoded byte array
+ */
+ public byte[] encodeCharsetToBytes(Charset charset) {
+ return encodeCharset(charset).getBytes(charset);
}
/**
@@ -1412,26 +1839,19 @@ public String encode(BinaryToTextEncoding.Encoder encoder) {
* @return copy of internal array as list
*/
public List toList() {
- return Util.toList(internalArray());
- }
-
- /**
- * @deprecated renamed API, use {@link #toBoxedArray()} instead - will be removed in v1.0+
- * @return see {@link #toBoxedArray()}
- */
- @Deprecated
- public Byte[] toObjectArray() {
- return toBoxedArray();
+ return Util.Converter.toList(internalArray());
}
/**
* Returns a copy of the internal byte-array as boxed primitive array.
* This requires a time and space complexity of O(n).
+ *
+ * Note: this method was previously called toObjectArray()
*
* @return copy of internal array as object array
*/
public Byte[] toBoxedArray() {
- return Util.toBoxedArray(internalArray());
+ return Util.Converter.toBoxedArray(internalArray());
}
/**
@@ -1447,7 +1867,7 @@ public BitSet toBitSet() {
* The internal byte array wrapped in a {@link BigInteger} instance.
*
* If the internal byte order is {@link ByteOrder#LITTLE_ENDIAN}, a copy of the internal
- * array will be reversed and used as backing array with the big integer. Otherwise the internal
+ * array will be reversed and used as backing array with the big integer. Otherwise, the internal
* array will be used directly.
*
* @return big integer
@@ -1460,6 +1880,20 @@ public BigInteger toBigInteger() {
}
}
+ /**
+ * Creates a {@link UUID} instance of the internal byte array. This requires the internal array to be exactly 16 bytes. Takes the first
+ * 8 byte as mostSigBits and the last 8 byte as leastSigBits. There is no validation of version/type, just passes the raw bytes
+ * to a {@link UUID} constructor.
+ *
+ * @return newly created UUID
+ * @throws IllegalArgumentException if byte array has length not equal to 16
+ */
+ public UUID toUUID() {
+ Util.Validation.checkExactLength(length(), 16, "UUID");
+ ByteBuffer byteBuffer = buffer();
+ return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
+ }
+
/**
* If the underlying byte array is exactly 1 byte / 8 bit long, returns signed two-complement
* representation for a Java byte value.
@@ -1467,11 +1901,11 @@ public BigInteger toBigInteger() {
* If you just want to get the first element as {@code byte}, see {@link #byteAt(int)}, using index zero.
*
* @return the byte representation
- * @throws IllegalStateException if byte array has length not equal to 1
+ * @throws IllegalArgumentException if byte array has length not equal to 1
* @see Primitive Types
*/
public byte toByte() {
- Util.checkExactLength(length(), 1, "byte");
+ Util.Validation.checkExactLength(length(), 1, "byte");
return internalArray()[0];
}
@@ -1482,11 +1916,11 @@ public byte toByte() {
* If you just want to get the first element as {@code byte}, see {@link #byteAt(int)}, using index zero.
*
* @return the unsigned byte representation wrapped in an int
- * @throws IllegalStateException if byte array has length not equal to 1
+ * @throws IllegalArgumentException if byte array has length not equal to 1
* @see Primitive Types
*/
public int toUnsignedByte() {
- Util.checkExactLength(length(), 1, "unsigned byte");
+ Util.Validation.checkExactLength(length(), 1, "unsigned byte");
return unsignedByteAt(0);
}
@@ -1497,11 +1931,11 @@ public int toUnsignedByte() {
* If you just want to get the first 2 bytes as {@code char}, see {@link #charAt(int)} using index zero.
*
* @return the int representation
- * @throws IllegalStateException if byte array has length not equal to 2
+ * @throws IllegalArgumentException if byte array has length not equal to 2
* @see Primitive Types
*/
public char toChar() {
- Util.checkExactLength(length(), 2, "char");
+ Util.Validation.checkExactLength(length(), 2, "char");
return charAt(0);
}
@@ -1512,11 +1946,11 @@ public char toChar() {
* If you just want to get the first 2 bytes as {@code short}, see {@link #shortAt(int)} using index zero.
*
* @return the int representation
- * @throws IllegalStateException if byte array has length not equal to 2
+ * @throws IllegalArgumentException if byte array has length not equal to 2
* @see Primitive Types
*/
public short toShort() {
- Util.checkExactLength(length(), 2, "short");
+ Util.Validation.checkExactLength(length(), 2, "short");
return shortAt(0);
}
@@ -1527,14 +1961,33 @@ public short toShort() {
* If you just want to get the first 4 bytes as {@code int}, see {@link #intAt(int)} using index zero.
*
* @return the int representation
- * @throws IllegalStateException if byte array has length not equal to 4
+ * @throws IllegalArgumentException if byte array has length not equal to 4
* @see Primitive Types
*/
public int toInt() {
- Util.checkExactLength(length(), 4, "int");
+ Util.Validation.checkExactLength(length(), 4, "int");
return intAt(0);
}
+ /**
+ * Converts the internal byte array to an int array, that is, every 4 bytes will be packed into a single int.
+ *
+ * E.g. 4 bytes will be packed to a length 1 int array:
+ *
+ * [b1, b2, b3, b4] = [int1]
+ *
+ *
+ * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to int,
+ * which means the byte array length must be dividable by 4 without rest.
+ *
+ * @return new int[] instance representing this byte array
+ * @throws IllegalArgumentException if internal byte length mod 4 != 0
+ */
+ public int[] toIntArray() {
+ Util.Validation.checkModLength(length(), 4, "creating an int array");
+ return Util.Converter.toIntArray(internalArray(), byteOrder);
+ }
+
/**
* If the underlying byte array is exactly 8 byte / 64 bit long, return signed two-complement
* representation for a Java signed long integer value. The output is dependent on the set {@link #byteOrder()}.
@@ -1542,40 +1995,137 @@ public int toInt() {
* If you just want to get the first 4 bytes as {@code long}, see {@link #longAt(int)} using index zero.
*
* @return the long representation
- * @throws IllegalStateException if byte array has length not equal to 8
+ * @throws IllegalArgumentException if byte array has length not equal to 8
* @see Primitive Types
*/
public long toLong() {
- Util.checkExactLength(length(), 8, "long");
+ Util.Validation.checkExactLength(length(), 8, "long");
return longAt(0);
}
+ /**
+ * Converts the internal byte array to a long array, that is, every 8 bytes will be packed into a single long.
+ *
+ * E.g. 8 bytes will be packed to a length 1 long array:
+ *
+ * [b1, b2, b3, b4, b5, b6, b7, b8] = [int1]
+ *
+ *
+ * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to long,
+ * which means the byte array length must be dividable by 8 without rest.
+ *
+ * @return new long[] instance representing this byte array
+ * @throws IllegalArgumentException if internal byte length mod 8 != 0
+ */
+ public long[] toLongArray() {
+ Util.Validation.checkModLength(length(), 8, "creating an long array");
+ return Util.Converter.toLongArray(internalArray(), byteOrder);
+ }
+
/**
* If the underlying byte array is exactly 4 byte / 32 bit long, return the
* representation for a Java float value. The output is dependent on the set {@link #byteOrder()}.
*
* @return the float representation
- * @throws IllegalStateException if byte array has length not equal to 4
+ * @throws IllegalArgumentException if byte array has length not equal to 4
* @see Primitive Types
*/
public float toFloat() {
- Util.checkExactLength(length(), 4, "float");
+ Util.Validation.checkExactLength(length(), 4, "float");
return internalBuffer().getFloat();
}
+ /**
+ * Converts the internal byte array to a float array, that is, every 4 bytes will be packed into a single float.
+ *
+ * E.g. 4 bytes will be packed to a length 1 float array:
+ *
+ * [b1, b2, b3, b4] = [float1]
+ *
+ *
+ * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to float,
+ * which means the byte array length must be dividable by 4 without rest.
+ *
+ * @return new float[] instance representing this byte array
+ * @throws IllegalArgumentException if internal byte length mod 4 != 0
+ */
+ public float[] toFloatArray() {
+ Util.Validation.checkModLength(length(), 4, "creating an float array");
+ return Util.Converter.toFloatArray(internalArray(), byteOrder);
+ }
+
/**
* If the underlying byte array is exactly 8 byte / 64 bit long, return the
* representation for a Java double value. The output is dependent on the set {@link #byteOrder()}.
*
* @return the double representation
- * @throws IllegalStateException if byte array has length not equal to 8
+ * @throws IllegalArgumentException if byte array has length not equal to 8
* @see Primitive Types
*/
public double toDouble() {
- Util.checkExactLength(length(), 8, "double");
+ Util.Validation.checkExactLength(length(), 8, "double");
return internalBuffer().getDouble();
}
+ /**
+ * Converts the internal byte array to a double array, that is, every 8 bytes will be packed into a single double.
+ *
+ * E.g. 8 bytes will be packed to a length 1 double array:
+ *
+ * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to double,
+ * which means the byte array length must be dividable by 8 without rest.
+ *
+ * @return new double[] instance representing this byte array
+ * @throws IllegalArgumentException if internal byte length mod 8 != 0
+ */
+ public double[] toDoubleArray() {
+ Util.Validation.checkModLength(length(), 8, "creating an double array");
+ return Util.Converter.toDoubleArray(internalArray(), byteOrder);
+ }
+
+ /**
+ * Converts the internal byte array to a short array, that is, every 2 bytes will be packed into a single short.
+ *
+ * E.g. 2 bytes will be packed to a length 1 short array:
+ *
+ * [b1, b2] = [short1]
+ *
+ *
+ * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to short,
+ * which means the byte array length must be dividable by 2 without rest.
+ *
+ * @return new short[] instance representing this byte array
+ * @throws IllegalArgumentException if internal byte length mod 2 != 0
+ */
+ public short[] toShortArray() {
+ Util.Validation.checkModLength(length(), 2, "creating a short array");
+ return Util.Converter.toShortArray(internalArray(), byteOrder);
+ }
+
+ /**
+ * Decodes the internal byte array to UTF-8 char array.
+ * This implementation will not internally create a {@link String}.
+ *
+ * @return char array
+ */
+ public char[] toCharArray() {
+ return toCharArray(StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Decodes the internal byte array with given charset to a char array.
+ * This implementation will not internally create a {@link String}.
+ *
+ * @param charset to use for decoding
+ * @return char array
+ */
+ public char[] toCharArray(Charset charset) {
+ return Util.Converter.byteToCharArray(internalArray(), charset, byteOrder);
+ }
+
/**
* Compares this bytes instance to another.
*
@@ -1585,7 +2135,7 @@ public double toDouble() {
* Pairs of {@code byte} elements are compared as if by invoking
* {@link Byte#compare(byte, byte)}.
*
- * Uses {@link ByteBuffer#compareTo(Object)} internally.
+ * Uses {@link ByteBuffer#compareTo(ByteBuffer)} internally.
*
* @return A negative integer, zero, or a positive integer as this buffer
* is less than, equal to, or greater than the given buffer
@@ -1609,7 +2159,7 @@ public boolean equals(Object o) {
Bytes bytes = (Bytes) o;
if (!Arrays.equals(byteArray, bytes.byteArray)) return false;
- return byteOrder != null ? byteOrder.equals(bytes.byteOrder) : bytes.byteOrder == null;
+ return Objects.equals(byteOrder, bytes.byteOrder);
}
/**
@@ -1626,14 +2176,14 @@ public boolean equals(byte[] anotherArray) {
* Compares the inner array with given array. The comparison is done in constant time, therefore
* will not break on the first mismatch. This method is useful to prevent some side-channel attacks,
* but is slower on average.
- *
- * This implementation uses the algorithm suggested in https://codahale.com/a-lesson-in-timing-attacks/
+ *
+ * This implementation uses the algorithm suggested in a-lesson-in-timing-attacks
*
* @param anotherArray to compare with
* @return true if {@link Arrays#equals(byte[], byte[])} returns true on given and internal array
*/
public boolean equalsConstantTime(byte[] anotherArray) {
- return anotherArray != null && Util.constantTimeEquals(internalArray(), anotherArray);
+ return anotherArray != null && Util.Byte.constantTimeEquals(internalArray(), anotherArray);
}
/**
@@ -1644,7 +2194,7 @@ public boolean equalsConstantTime(byte[] anotherArray) {
* @return true if both array have same length and every byte element is the same
*/
public boolean equals(Byte[] anotherArray) {
- return Util.equals(internalArray(), anotherArray);
+ return Util.Obj.equals(internalArray(), anotherArray);
}
/**
@@ -1655,7 +2205,7 @@ public boolean equals(Byte[] anotherArray) {
* @return true if both array have same length and every byte element is the same
*/
public boolean equals(ByteBuffer buffer) {
- return buffer != null && byteOrder == buffer.order() && ByteBuffer.wrap(internalArray()).order(byteOrder).equals(buffer);
+ return buffer != null && byteOrder == buffer.order() && internalBuffer().equals(buffer);
}
/**
@@ -1670,20 +2220,18 @@ public boolean equalsContent(Bytes other) {
@Override
public int hashCode() {
- int result = Arrays.hashCode(byteArray);
- result = 31 * result + (byteOrder != null ? byteOrder.hashCode() : 0);
- return result;
+ return Util.Obj.hashCode(internalArray(), byteOrder());
}
/**
- * A memory safe toString implementation, which only shows the byte length and at most 8 bytes preview in hex
+ * A constant length output toString() implementation, which only shows the byte length and at most 8 bytes preview in hex
* representation.
*
* @return string representation
*/
@Override
public String toString() {
- return Util.toString(this);
+ return Util.Obj.toString(this);
}
@Override
diff --git a/src/main/java/at/favre/lib/bytes/BytesTransformer.java b/src/main/java/at/favre/lib/bytes/BytesTransformer.java
index 41111c2..efdd12c 100644
--- a/src/main/java/at/favre/lib/bytes/BytesTransformer.java
+++ b/src/main/java/at/favre/lib/bytes/BytesTransformer.java
@@ -21,6 +21,7 @@
package at.favre.lib.bytes;
+import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
@@ -60,10 +61,8 @@ public enum Mode {
private final Mode mode;
BitWiseOperatorTransformer(byte[] secondArray, Mode mode) {
- Objects.requireNonNull(secondArray, "the second byte array must not be null");
- Objects.requireNonNull(mode, "passed bitwise mode must not be null");
- this.secondArray = secondArray;
- this.mode = mode;
+ this.secondArray = Objects.requireNonNull(secondArray, "the second byte array must not be null");
+ this.mode = Objects.requireNonNull(mode, "passed bitwise mode must not be null");
}
@Override
@@ -133,12 +132,12 @@ public enum Type {
private final int shiftCount;
private final Type type;
+ private final ByteOrder byteOrder;
- ShiftTransformer(int shiftCount, Type type) {
- Objects.requireNonNull(type, "passed shift type must not be null");
-
+ ShiftTransformer(int shiftCount, Type type, ByteOrder byteOrder) {
this.shiftCount = shiftCount;
- this.type = type;
+ this.type = Objects.requireNonNull(type, "passed shift type must not be null");
+ this.byteOrder = Objects.requireNonNull(byteOrder, "passed byteOrder type must not be null");
}
@Override
@@ -147,10 +146,10 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
switch (type) {
case RIGHT_SHIFT:
- return Util.shiftRight(out, shiftCount);
+ return Util.Byte.shiftRight(out, shiftCount, byteOrder);
default:
case LEFT_SHIFT:
- return Util.shiftLeft(out, shiftCount);
+ return Util.Byte.shiftLeft(out, shiftCount, byteOrder);
}
}
@@ -169,13 +168,12 @@ final class ConcatTransformer implements BytesTransformer {
private final byte[] secondArray;
ConcatTransformer(byte[] secondArrays) {
- Objects.requireNonNull(secondArrays, "the second byte array must not be null");
- this.secondArray = secondArrays;
+ this.secondArray = Objects.requireNonNull(secondArrays, "the second byte array must not be null");
}
@Override
public byte[] transform(byte[] currentArray, boolean inPlace) {
- return Util.concat(currentArray, secondArray);
+ return Util.Byte.concat(currentArray, secondArray);
}
@Override
@@ -191,7 +189,7 @@ final class ReverseTransformer implements BytesTransformer {
@Override
public byte[] transform(byte[] currentArray, boolean inPlace) {
byte[] out = inPlace ? currentArray : Bytes.from(currentArray).array();
- Util.reverse(out, 0, out.length);
+ Util.Byte.reverse(out, 0, out.length);
return out;
}
@@ -233,7 +231,7 @@ public boolean supportInPlaceTransformation() {
* contain identical values. For any indices that are valid in the
* copy but not the original, the copy will contain {@code (byte)0}.
*
- * If if the internal array will be grown, zero bytes will be added on the left,
+ * If the internal array will be increased, zero bytes will be added on the left,
* keeping the value the same.
*/
final class ResizeTransformer implements BytesTransformer {
@@ -267,10 +265,12 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
byte[] resizedArray = new byte[newSize];
if (mode == Mode.RESIZE_KEEP_FROM_MAX_LENGTH) {
+ int max = Math.max(0, Math.abs(newSize - currentArray.length));
+
if (newSize > currentArray.length) {
- System.arraycopy(currentArray, 0, resizedArray, Math.max(0, Math.abs(newSize - currentArray.length)), Math.min(newSize, currentArray.length));
+ System.arraycopy(currentArray, 0, resizedArray, max, currentArray.length);
} else {
- System.arraycopy(currentArray, Math.max(0, Math.abs(newSize - currentArray.length)), resizedArray, Math.min(0, Math.abs(newSize - currentArray.length)), Math.min(newSize, currentArray.length));
+ System.arraycopy(currentArray, max, resizedArray, 0, newSize);
}
} else {
System.arraycopy(currentArray, 0, resizedArray, 0, Math.min(currentArray.length, resizedArray.length));
@@ -326,6 +326,8 @@ public boolean supportInPlaceTransformation() {
* Converts to hash
*/
class MessageDigestTransformer implements BytesTransformer {
+ static final String ALGORITHM_MD5 = "MD5";
+ static final String ALGORITHM_SHA_1 = "SHA-1";
static final String ALGORITHM_SHA_256 = "SHA-256";
private final MessageDigest messageDigest;
diff --git a/src/main/java/at/favre/lib/bytes/BytesTransformers.java b/src/main/java/at/favre/lib/bytes/BytesTransformers.java
index 656fd19..c1914c0 100644
--- a/src/main/java/at/favre/lib/bytes/BytesTransformers.java
+++ b/src/main/java/at/favre/lib/bytes/BytesTransformers.java
@@ -1,10 +1,14 @@
package at.favre.lib.bytes;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.security.SecureRandom;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Random;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import java.util.zip.GZIPInputStream;
@@ -13,6 +17,7 @@
/**
* Collection of additional {@link BytesTransformer} for more specific use cases
*/
+@SuppressWarnings("WeakerAccess")
public final class BytesTransformers {
private BytesTransformers() {
@@ -39,7 +44,8 @@ public static BytesTransformer shuffle(Random random) {
}
/**
- * Create a {@link BytesTransformer} which sorts the internal byte array with it's natural ordering.
+ * Create a {@link BytesTransformer} which sorts the internal byte array with its natural ordering treating
+ * each byte as signed byte (-128...127). Using inplace sorting, this can be reasonable fast.
*
* @return transformer
*/
@@ -47,8 +53,24 @@ public static BytesTransformer sort() {
return new SortTransformer();
}
+ /**
+ * Create a {@link BytesTransformer} which sorts the internal byte array with its natural ordering treating
+ * each byte as unsigned byte (0...255). That is, the byte string {@code ff} sorts after {@code 00}.
+ *
+ * Note: this requires 2 copies of the internal array and a lot of unboxing due to
+ * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays.
+ *
+ * @return transformer
+ */
+ public static BytesTransformer sortUnsigned() {
+ return new SortTransformer(new SortTransformer.UnsignedByteComparator());
+ }
+
/**
* Create a {@link BytesTransformer} which sorts the internal byte array according to given comparator.
+ *
+ * Note: this requires 2 copies of the internal array and a lot of unboxing due to
+ * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays.
*
* @param comparator to sort the bytes
* @return transformer
@@ -110,6 +132,37 @@ public static BytesTransformer decompressGzip() {
return new GzipCompressor(false);
}
+ /**
+ * Create a {@link BytesTransformer} which returns the HMAC-SHA1 with given key, of the target byte array
+ *
+ * @param key to use for HMAC
+ * @return hmac
+ */
+ public static BytesTransformer hmacSha1(byte[] key) {
+ return new HmacTransformer(key, HmacTransformer.HMAC_SHA1);
+ }
+
+ /**
+ * Create a {@link BytesTransformer} which returns the HMAC-SHA256 with given key, of the target byte array
+ *
+ * @param key to use for HMAC
+ * @return hmac
+ */
+ public static BytesTransformer hmacSha256(byte[] key) {
+ return new HmacTransformer(key, HmacTransformer.HMAC_SHA256);
+ }
+
+ /**
+ * Create a {@link BytesTransformer} which returns the HMAC with given key, algorithm of the target byte array
+ *
+ * @param key to use for HMAC
+ * @param algorithmName e.g. 'HmacSHA256' - check if the algorithm is supported on your JVM/runtime
+ * @return hmac (length depends on algorithm)
+ */
+ public static BytesTransformer hmac(byte[] key, String algorithmName) {
+ return new HmacTransformer(key, algorithmName);
+ }
+
/**
* Shuffles the internal byte array
*/
@@ -124,7 +177,7 @@ public static final class ShuffleTransformer implements BytesTransformer {
@Override
public byte[] transform(byte[] currentArray, boolean inPlace) {
byte[] out = inPlace ? currentArray : Bytes.from(currentArray).array();
- Util.shuffle(out, random);
+ Util.Byte.shuffle(out, random);
return out;
}
@@ -132,6 +185,7 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
public boolean supportInPlaceTransformation() {
return true;
}
+
}
/**
@@ -156,9 +210,9 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
return out;
} else {
//no in-place implementation with comparator
- List list = Bytes.wrap(currentArray).toList();
- Collections.sort(list, comparator);
- return Bytes.from(list).array();
+ Byte[] boxedArray = Bytes.wrap(currentArray).toBoxedArray();
+ Arrays.sort(boxedArray, comparator);
+ return Bytes.from(boxedArray).array();
}
}
@@ -166,33 +220,33 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
public boolean supportInPlaceTransformation() {
return comparator == null;
}
+
+ /**
+ * Converting each byte into unsigned version and comparing it (0...255) vs (-128..127)
+ */
+ static final class UnsignedByteComparator implements Comparator {
+ @Override
+ public int compare(Byte o1, Byte o2) {
+ int byteA = o1 & 0xff;
+ int byteB = o2 & 0xff;
+ if (byteA == byteB) return 0;
+ return byteA < byteB ? -1 : 1;
+ }
+ }
+
}
/**
* Adds or converts to arbitrary checksum
*/
public static final class ChecksumTransformer implements BytesTransformer {
- /**
- * Definitions of the mode
- */
- public enum Mode {
- /**
- * Appends checksum to given byte array
- */
- APPEND,
- /**
- * Transforms byte array and returns only checksum
- */
- TRANSFORM
- }
-
private final Checksum checksum;
private final Mode mode;
private final int checksumLengthByte;
ChecksumTransformer(Checksum checksum, Mode mode, int checksumLengthByte) {
if (checksumLengthByte <= 0 || checksumLengthByte > 8)
- throw new IllegalArgumentException("checksumlength must be between 1 and 8 bytes");
+ throw new IllegalArgumentException("checksum length must be between 1 and 8 bytes");
Objects.requireNonNull(checksum, "checksum instance must not be null");
this.checksum = checksum;
@@ -216,6 +270,20 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
public boolean supportInPlaceTransformation() {
return false;
}
+
+ /**
+ * Definitions of the mode
+ */
+ public enum Mode {
+ /**
+ * Appends checksum to given byte array
+ */
+ APPEND,
+ /**
+ * Transforms byte array and returns only checksum
+ */
+ TRANSFORM
+ }
}
/**
@@ -234,64 +302,69 @@ public byte[] transform(byte[] currentArray, boolean inPlace) {
}
private byte[] decompress(byte[] compressedContent) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- GZIPInputStream gzipInputStream = null;
- byte[] returnBuffer;
- try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.max(32, compressedContent.length / 2));
+
+ try (GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedContent))) {
int len;
byte[] buffer = new byte[4 * 1024];
- gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedContent));
while ((len = gzipInputStream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
- gzipInputStream.close();
- returnBuffer = bos.toByteArray();
- bos.close();
- return returnBuffer;
+ return bos.toByteArray();
} catch (Exception e) {
throw new IllegalStateException("could not decompress gzip", e);
- } finally {
- try {
- bos.close();
- } catch (IOException ignore) {
- }
-
- if (gzipInputStream != null) {
- try {
- gzipInputStream.close();
- } catch (IOException ignore) {
- }
- }
}
}
private byte[] compress(byte[] content) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length);
- GZIPOutputStream gzipOutputStream = null;
- byte[] returnBuffer;
- try {
- gzipOutputStream = new GZIPOutputStream(bos);
+
+ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bos)) {
gzipOutputStream.write(content);
- gzipOutputStream.close();
- returnBuffer = bos.toByteArray();
- bos.close();
- return returnBuffer;
} catch (Exception e) {
throw new IllegalStateException("could not compress gzip", e);
- } finally {
- try {
- bos.close();
- } catch (IOException ignore) {
- }
+ }
- if (gzipOutputStream != null) {
- try {
- gzipOutputStream.close();
- } catch (IOException ignore) {
- }
- }
+ return bos.toByteArray();
+ }
+
+ @Override
+ public boolean supportInPlaceTransformation() {
+ return false;
+ }
+ }
+
+ /**
+ * HMAC transformer
+ */
+ public static final class HmacTransformer implements BytesTransformer {
+ static final String HMAC_SHA1 = "HmacSHA1";
+ static final String HMAC_SHA256 = "HmacSHA256";
+
+ private final byte[] secretKey;
+ private final String macAlgorithmName;
+
+ /**
+ * Create a new hmac transformer
+ *
+ * @param secretKey to use as key
+ * @param macAlgorithmName hash algorithm e.g. 'HmacSHA256'
+ */
+ HmacTransformer(byte[] secretKey, String macAlgorithmName) {
+ this.macAlgorithmName = macAlgorithmName;
+ this.secretKey = secretKey;
+ }
+
+ @Override
+ public byte[] transform(byte[] currentArray, boolean inPlace) {
+ try {
+ Mac mac = Mac.getInstance(macAlgorithmName);
+ mac.init(new SecretKeySpec(secretKey, macAlgorithmName));
+ return mac.doFinal(currentArray);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
}
}
diff --git a/src/main/java/at/favre/lib/bytes/BytesValidators.java b/src/main/java/at/favre/lib/bytes/BytesValidators.java
index 479dd18..0d0f30f 100644
--- a/src/main/java/at/favre/lib/bytes/BytesValidators.java
+++ b/src/main/java/at/favre/lib/bytes/BytesValidators.java
@@ -27,6 +27,7 @@
/**
* Util and easy access for {@link BytesValidators}
*/
+@SuppressWarnings("WeakerAccess")
public final class BytesValidators {
private BytesValidators() {
@@ -36,7 +37,7 @@ private BytesValidators() {
* Checks the length of a byte array
*
* @param byteLength to check against
- * @return true if longer or equal to given value
+ * @return validator that returns true if longer or equal to given value
*/
public static BytesValidator atLeast(int byteLength) {
return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.GREATER_OR_EQ_THAN);
@@ -46,7 +47,7 @@ public static BytesValidator atLeast(int byteLength) {
* Checks the length of a byte array
*
* @param byteLength to check against
- * @return true if smaller or equal to given value
+ * @return validator that returns true if smaller or equal to given value
*/
public static BytesValidator atMost(int byteLength) {
return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.SMALLER_OR_EQ_THAN);
@@ -56,7 +57,7 @@ public static BytesValidator atMost(int byteLength) {
* Checks the length of a byte array
*
* @param byteLength to check against
- * @return true if equal to given value
+ * @return validator that returns true if equal to given value
*/
public static BytesValidator exactLength(int byteLength) {
return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.EXACT);
@@ -66,7 +67,7 @@ public static BytesValidator exactLength(int byteLength) {
* Checks individual byte content
*
* @param refByte to check against
- * @return true if array only consists of refByte
+ * @return validator that returns true if array only consists of refByte
*/
public static BytesValidator onlyOf(byte refByte) {
return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.ONLY_OF);
@@ -76,7 +77,7 @@ public static BytesValidator onlyOf(byte refByte) {
* Checks individual byte content
*
* @param refByte to check against
- * @return true if array has at least one byte that is not refByte
+ * @return validator that returns true if array has at least one byte that is not refByte
*/
public static BytesValidator notOnlyOf(byte refByte) {
return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NOT_ONLY_OF);
@@ -86,7 +87,7 @@ public static BytesValidator notOnlyOf(byte refByte) {
* Checks if the internal byte array starts with given bytes
*
* @param startsWithBytes the supposed prefix
- * @return true all startsWithBytes match the first bytes in the internal array
+ * @return validator that returns true all startsWithBytes match the first bytes in the internal array
*/
public static BytesValidator startsWith(byte... startsWithBytes) {
return new BytesValidator.PrePostFix(true, startsWithBytes);
@@ -96,7 +97,7 @@ public static BytesValidator startsWith(byte... startsWithBytes) {
* Checks if the internal byte array ends with given bytes
*
* @param endsWithBytes the supposed postfix
- * @return true all startsWithBytes match the first bytes in the internal array
+ * @return validator that returns true all startsWithBytes match the first bytes in the internal array
*/
public static BytesValidator endsWith(byte... endsWithBytes) {
return new BytesValidator.PrePostFix(false, endsWithBytes);
@@ -106,18 +107,17 @@ public static BytesValidator endsWith(byte... endsWithBytes) {
* Checks individual byte content
*
* @param refByte to check against
- * @return true if array has no value refByte
+ * @return validator that returns true if array has no value refByte
*/
public static BytesValidator noneOf(byte refByte) {
return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NONE_OF);
}
-
/**
* This will execute all passed validators and returns true if at least one returns true (i.e. OR concatenation)
*
* @param validators at least one validator must be passed
- * @return true if at least one validator returns true
+ * @return validator that returns true if at least one validator returns true
*/
public static BytesValidator or(BytesValidator... validators) {
return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.OR);
@@ -127,7 +127,7 @@ public static BytesValidator or(BytesValidator... validators) {
* This will execute all passed validators and returns true if all return true (i.e. AND concatenation)
*
* @param validators at least one validator must be passed
- * @return true if all return true
+ * @return validator that returns true if all return true
*/
public static BytesValidator and(BytesValidator... validators) {
return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.AND);
diff --git a/src/main/java/at/favre/lib/bytes/MutableBytes.java b/src/main/java/at/favre/lib/bytes/MutableBytes.java
index 808bd23..270ae29 100644
--- a/src/main/java/at/favre/lib/bytes/MutableBytes.java
+++ b/src/main/java/at/favre/lib/bytes/MutableBytes.java
@@ -32,12 +32,33 @@
* Adds additional mutator, which may change the internal array in-place, like {@link #wipe()}
*/
@SuppressWarnings("WeakerAccess")
-public final class MutableBytes extends Bytes {
+public final class MutableBytes extends Bytes implements AutoCloseable {
MutableBytes(byte[] byteArray, ByteOrder byteOrder) {
super(byteArray, byteOrder, new Factory());
}
+ /**
+ * Creates a new instance with an empty array filled with zeros.
+ *
+ * @param length of the internal array
+ * @return new instance
+ */
+ public static MutableBytes allocate(int length) {
+ return allocate(length, (byte) 0);
+ }
+
+ /**
+ * Creates a new instance with an empty array filled with given defaultValue
+ *
+ * @param length of the internal array
+ * @param defaultValue to fill with
+ * @return new instance
+ */
+ public static MutableBytes allocate(int length, byte defaultValue) {
+ return Bytes.allocate(length, defaultValue).mutable();
+ }
+
@Override
public boolean isMutable() {
return true;
@@ -48,11 +69,21 @@ public boolean isMutable() {
*
* @param newArray used to overwrite internal
* @return this instance
- * @throws IndexOutOfBoundsException if newArray.length > internal length
+ * @throws IndexOutOfBoundsException if newArray.length() > internal length
*/
public MutableBytes overwrite(byte[] newArray) {
- overwrite(newArray, 0);
- return this;
+ return overwrite(newArray, 0);
+ }
+
+ /**
+ * Uses given Bytes array to overwrite internal array
+ *
+ * @param newBytes used to overwrite internal
+ * @return this instance
+ * @throws IndexOutOfBoundsException if newArray.length() > internal length
+ */
+ public MutableBytes overwrite(Bytes newBytes) {
+ return overwrite(newBytes, 0);
}
/**
@@ -61,7 +92,7 @@ public MutableBytes overwrite(byte[] newArray) {
* @param newArray used to overwrite internal
* @param offsetInternalArray index of the internal array to start overwriting
* @return this instance
- * @throws IndexOutOfBoundsException if newArray.length + offsetInternalArray > internal length
+ * @throws IndexOutOfBoundsException if newArray.length() + offsetInternalArray > internal length
*/
public MutableBytes overwrite(byte[] newArray, int offsetInternalArray) {
Objects.requireNonNull(newArray, "must provide non-null array as source");
@@ -69,6 +100,18 @@ public MutableBytes overwrite(byte[] newArray, int offsetInternalArray) {
return this;
}
+ /**
+ * Uses given Bytes array to overwrite internal array.
+ *
+ * @param newBytes used to overwrite internal
+ * @param offsetInternalArray index of the internal array to start overwriting
+ * @return this instance
+ * @throws IndexOutOfBoundsException if newBytes.length() + offsetInternalArray > internal length
+ */
+ public MutableBytes overwrite(Bytes newBytes, int offsetInternalArray) {
+ return overwrite(Objects.requireNonNull(newBytes, "must provide non-null array as source").array(), offsetInternalArray);
+ }
+
/**
* Sets new byte to given index
*
@@ -87,8 +130,7 @@ public MutableBytes setByteAt(int index, byte newByte) {
* @return this instance
*/
public MutableBytes wipe() {
- fill((byte) 0);
- return this;
+ return fill((byte) 0);
}
/**
@@ -108,8 +150,7 @@ public MutableBytes fill(byte fillByte) {
* @return this instance
*/
public MutableBytes secureWipe() {
- secureWipe(new SecureRandom());
- return this;
+ return secureWipe(new SecureRandom());
}
/**
@@ -119,13 +160,39 @@ public MutableBytes secureWipe() {
* @return this instance
*/
public MutableBytes secureWipe(SecureRandom random) {
- Objects.requireNonNull(random, "must be non-null random");
+ Objects.requireNonNull(random, "random param must not be null");
if (length() > 0) {
random.nextBytes(internalArray());
}
return this;
}
+ /**
+ * Convert this instance to an immutable version with the same reference of the internal array and byte-order.
+ * If the mutable instance is kept, it can be used to alter the internal array of the just created instance, so be
+ * aware.
+ *
+ * @return immutable version of this instance
+ */
+ public Bytes immutable() {
+ return Bytes.wrap(internalArray(), byteOrder());
+ }
+
+ @Override
+ public int hashCode() {
+ return Util.Obj.hashCode(internalArray(), byteOrder());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public void close() {
+ secureWipe();
+ }
+
/**
* Factory creating mutable byte types
*/
diff --git a/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java b/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java
index 4959c64..5baefee 100644
--- a/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java
+++ b/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java
@@ -34,7 +34,7 @@
public final class ReadOnlyBytes extends Bytes {
/**
- * Creates a new immutable instance
+ * Creates a new read-only instance
*
* @param byteArray internal byte array
* @param byteOrder the internal byte order - this is used to interpret given array, not to change it
diff --git a/src/main/java/at/favre/lib/bytes/Util.java b/src/main/java/at/favre/lib/bytes/Util.java
index d3a8c74..ac234bb 100644
--- a/src/main/java/at/favre/lib/bytes/Util.java
+++ b/src/main/java/at/favre/lib/bytes/Util.java
@@ -22,7 +22,9 @@
package at.favre.lib.bytes;
import java.io.*;
-import java.nio.ByteBuffer;
+import java.nio.*;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;
@@ -30,477 +32,1176 @@
* Common Util methods to convert or modify byte arrays
*/
final class Util {
- private Util() {
- }
/**
- * Returns the values from each provided byteArray combined into a single byteArray.
- * For example, {@code append(new byte[] {a, b}, new byte[] {}, new
- * byte[] {c}} returns the byteArray {@code {a, b, c}}.
- *
- * @param arrays zero or more {@code byte} arrays
- * @return a single byteArray containing all the values from the source arrays, in
- * order
+ * Util methods related general purpose byte utility.
*/
- static byte[] concat(byte[]... arrays) {
- int length = 0;
- for (byte[] array : arrays) {
- length += array.length;
- }
- byte[] result = new byte[length];
- int pos = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, 0, result, pos, array.length);
- pos += array.length;
- }
- return result;
- }
+ static final class Byte {
+ private Byte() {
+ }
- /**
- * Returns the index of the first appearance of the value {@code target} in
- * {@code array}.
- *
- * @param array an array of {@code byte} values, possibly empty
- * @param target a primitive {@code byte} value
- * @return the least index {@code i} for which {@code array[i] == target}, or
- * {@code -1} if no such index exists.
- */
- static int indexOf(byte[] array, byte target, int start, int end) {
- for (int i = start; i < end; i++) {
- if (array[i] == target) {
+ /**
+ * Returns the values from each provided byteArray combined into a single byteArray.
+ * For example, {@code append(new byte[] {a, b}, new byte[] {}, new
+ * byte[] {c}} returns the byteArray {@code {a, b, c}}.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param arrays zero or more {@code byte} arrays
+ * @return a single byteArray containing all the values from the source arrays, in
+ * order
+ */
+ static byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+ byte[] result = new byte[length];
+ int pos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
+ }
+
+ /**
+ * Combines a single argument with a vararg to a single array
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param firstByte first arg
+ * @param moreBytes varargs
+ * @return array containing all args
+ */
+ static byte[] concatVararg(byte firstByte, byte[] moreBytes) {
+ if (moreBytes == null) {
+ return new byte[]{firstByte};
+ } else {
+ return concat(new byte[]{firstByte}, moreBytes);
+ }
+ }
+
+ /**
+ * Returns the start position of the first occurrence of the specified {@code
+ * target} within {@code array}, or {@code -1} if there is no such occurrence.
+ *
+ *
More formally, returns the lowest index {@code i} such that {@code
+ * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly
+ * the same elements as {@code target}.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n*m)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array the array to search for the sequence {@code target}
+ * @param target the array to search for as a sub-sequence of {@code array}
+ */
+ static int indexOf(byte[] array, byte[] target, int start, int end) {
+ Objects.requireNonNull(array, "array must not be null");
+ Objects.requireNonNull(target, "target must not be null");
+ if (target.length == 0 || start < 0) {
+ return -1;
+ }
+
+ outer:
+ for (int i = start; i < Math.min(end, array.length - target.length + 1); i++) {
+ for (int j = 0; j < target.length; j++) {
+ if (array[i + j] != target[j]) {
+ continue outer;
+ }
+ }
return i;
}
+ return -1;
}
- return -1;
- }
- /**
- * Returns the start position of the first occurrence of the specified {@code
- * target} within {@code array}, or {@code -1} if there is no such occurrence.
- *
- *
More formally, returns the lowest index {@code i} such that {@code
- * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly
- * the same elements as {@code target}.
- *
- * @param array the array to search for the sequence {@code target}
- * @param target the array to search for as a sub-sequence of {@code array}
- */
- public static int indexOf(byte[] array, byte[] target) {
- Objects.requireNonNull(array, "array must not be null");
- Objects.requireNonNull(target, "target must not be null");
- if (target.length == 0) {
- return 0;
- }
-
- outer:
- for (int i = 0; i < array.length - target.length + 1; i++) {
- for (int j = 0; j < target.length; j++) {
- if (array[i + j] != target[j]) {
- continue outer;
+ /**
+ * Returns the index of the last appearance of the value {@code target} in
+ * {@code array}.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array an array of {@code byte} values, possibly empty
+ * @param target a primitive {@code byte} value
+ * @return the greatest index {@code i} for which {@code array[i] == target},
+ * or {@code -1} if no such index exists.
+ */
+ static int lastIndexOf(byte[] array, byte target, int start, int end) {
+ for (int i = end - 1; i >= start; i--) {
+ if (array[i] == target) {
+ return i;
}
}
- return i;
+ return -1;
}
- return -1;
- }
- /**
- * Returns the index of the last appearance of the value {@code target} in
- * {@code array}.
- *
- * @param array an array of {@code byte} values, possibly empty
- * @param target a primitive {@code byte} value
- * @return the greatest index {@code i} for which {@code array[i] == target},
- * or {@code -1} if no such index exists.
- */
- static int lastIndexOf(byte[] array, byte target, int start, int end) {
- for (int i = end - 1; i >= start; i--) {
- if (array[i] == target) {
- return i;
+ /**
+ * Counts the occurrence of target in the subject array
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to count in
+ * @param target to count
+ * @return number of times target is in subject
+ */
+ static int countByte(byte[] array, byte target) {
+ int count = 0;
+ for (byte b : array) {
+ if (b == target) {
+ count++;
+ }
}
+ return count;
}
- return -1;
- }
- /**
- * Copies a collection of {@code Byte} instances into a new array of
- * primitive {@code byte} values.
- *
- *
Elements are copied from the argument collection as if by {@code
- * collection.toArray()}. Calling this method is as thread-safe as calling
- * that method.
- *
- * @param collection a collection of {@code Byte} objects
- * @return an array containing the same values as {@code collection}, in the
- * same order, converted to primitives
- * @throws NullPointerException if {@code collection} or any of its elements
- * is null
- */
- static byte[] toArray(Collection collection) {
- Object[] boxedArray = collection.toArray();
- int len = boxedArray.length;
- byte[] array = new byte[len];
- for (int i = 0; i < len; i++) {
- array[i] = (Byte) boxedArray[i];
- }
- return array;
- }
+ /**
+ * Counts the times given pattern (i.e. an array) can be found in given array
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n*m)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to count in
+ * @param pattern to match in array
+ * @return number of times pattern is in subject
+ */
+ static int countByteArray(byte[] array, byte[] pattern) {
+ Objects.requireNonNull(pattern, "pattern must not be null");
+ if (pattern.length == 0 || array.length == 0) {
+ return 0;
+ }
+ int count = 0;
+ outer:
+ for (int i = 0; i < array.length - pattern.length + 1; i++) {
+ for (int j = 0; j < pattern.length; j++) {
+ if (array[i + j] != pattern[j]) {
+ continue outer;
+ }
+ }
+ count++;
+ }
+ return count;
+ }
- /**
- * Converts given array to list of boxed bytes. Will create a new list
- * and not reuse the array reference.
- *
- * @param array to convert
- * @return list with same length and content as array
- */
- static List toList(byte[] array) {
- List list = new ArrayList<>(array.length);
- for (byte b : array) {
- list.add(b);
+ /**
+ * Simple Durstenfeld shuffle.
+ * This will shuffle given array and will not make a copy, so beware.
+ *
+ *
+ *
+ * @param array to shuffle
+ * @param random used to derive entropy - use {@link java.security.SecureRandom} instance if you want this to be secure
+ */
+ static void shuffle(byte[] array, Random random) {
+ for (int i = array.length - 1; i > 0; i--) {
+ int index = random.nextInt(i + 1);
+ byte a = array[index];
+ array[index] = array[i];
+ array[i] = a;
+ }
}
- return list;
- }
- /**
- * Converts this primitive array to an boxed object array.
- * Will create a new array and not reuse the array reference.
- *
- * @param array to convert
- * @return new array
- */
- static Byte[] toBoxedArray(byte[] array) {
- Byte[] objectArray = new Byte[array.length];
- for (int i = 0; i < array.length; i++) {
- objectArray[i] = array[i];
+ /**
+ * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex}
+ * exclusive. This is equivalent to {@code
+ * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more
+ * efficient.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: true
+ *
+ *
+ *
+ * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or
+ * {@code toIndex > fromIndex}
+ */
+ static void reverse(byte[] array, int fromIndex, int toIndex) {
+ Objects.requireNonNull(array);
+ for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) {
+ byte tmp = array[i];
+ array[i] = array[j];
+ array[j] = tmp;
+ }
}
- return objectArray;
- }
- /**
- * Converts this object array to an primitives type array.
- * Will create a new array and not reuse the array reference.
- *
- * @param objectArray to convert
- * @return new array
- */
- static byte[] toPrimitiveArray(Byte[] objectArray) {
- byte[] primitivesArray = new byte[objectArray.length];
- for (int i = 0; i < objectArray.length; i++) {
- primitivesArray[i] = objectArray[i];
+ /**
+ * Light shift of whole byte array by shiftBitCount bits.
+ * This method will alter the input byte array.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: true
+ *
+ *
+ *
+ * @param byteArray to shift
+ * @param shiftBitCount how many bits to shift
+ * @param byteOrder endianness of given byte array
+ * @return shifted byte array
+ */
+ static byte[] shiftLeft(byte[] byteArray, int shiftBitCount, ByteOrder byteOrder) {
+ final int shiftMod = shiftBitCount % 8;
+ final byte carryMask = (byte) ((1 << shiftMod) - 1);
+ final int offsetBytes = (shiftBitCount / 8);
+
+ int sourceIndex;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ for (int i = 0; i < byteArray.length; i++) {
+ sourceIndex = i + offsetBytes;
+ if (sourceIndex >= byteArray.length) {
+ byteArray[i] = 0;
+ } else {
+ byte src = byteArray[sourceIndex];
+ byte dst = (byte) (src << shiftMod);
+ if (sourceIndex + 1 < byteArray.length) {
+ dst |= byteArray[sourceIndex + 1] >>> (8 - shiftMod) & carryMask & 0xff;
+ }
+ byteArray[i] = dst;
+ }
+ }
+ } else {
+ for (int i = byteArray.length - 1; i >= 0; i--) {
+ sourceIndex = i - offsetBytes;
+ if (sourceIndex < 0) {
+ byteArray[i] = 0;
+ } else {
+ byte src = byteArray[sourceIndex];
+ byte dst = (byte) (src << shiftMod);
+ if (sourceIndex - 1 >= 0) {
+ dst |= byteArray[sourceIndex - 1] >>> (8 - shiftMod) & carryMask & 0xff;
+ }
+ byteArray[i] = dst;
+ }
+ }
+ }
+ return byteArray;
}
- return primitivesArray;
- }
- /**
- * Creates a byte array from given int array.
- * The resulting byte array will have length intArray * 4.
- *
- * @param intArray to convert
- * @return resulting byte array
- */
- static byte[] toByteArray(int[] intArray) {
- byte[] primitivesArray = new byte[intArray.length * 4];
- for (int i = 0; i < intArray.length; i++) {
- byte[] intBytes = ByteBuffer.allocate(4).putInt(intArray[i]).array();
- System.arraycopy(intBytes, 0, primitivesArray, (i * 4), intBytes.length);
+ /**
+ * Unsigned/logical right shift of whole byte array by shiftBitCount bits.
+ * This method will alter the input byte array.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: true
+ *
+ *
+ *
+ * @param byteArray to shift
+ * @param shiftBitCount how many bits to shift
+ * @param byteOrder endianness of given byte array
+ * @return shifted byte array
+ */
+ static byte[] shiftRight(byte[] byteArray, int shiftBitCount, ByteOrder byteOrder) {
+ final int shiftMod = shiftBitCount % 8;
+ final byte carryMask = (byte) (0xFF << (8 - shiftMod));
+ final int offsetBytes = (shiftBitCount / 8);
+
+ int sourceIndex;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ for (int i = byteArray.length - 1; i >= 0; i--) {
+ sourceIndex = i - offsetBytes;
+ if (sourceIndex < 0) {
+ byteArray[i] = 0;
+ } else {
+ byte src = byteArray[sourceIndex];
+ byte dst = (byte) ((0xff & src) >>> shiftMod);
+ if (sourceIndex - 1 >= 0) {
+ dst |= byteArray[sourceIndex - 1] << (8 - shiftMod) & carryMask & 0xff;
+ }
+ byteArray[i] = dst;
+ }
+ }
+ } else {
+ for (int i = 0; i < byteArray.length; i++) {
+ sourceIndex = i + offsetBytes;
+ if (sourceIndex >= byteArray.length) {
+ byteArray[i] = 0;
+ } else {
+ byte src = byteArray[sourceIndex];
+ byte dst = (byte) ((0xff & src) >>> shiftMod);
+ if (sourceIndex + 1 < byteArray.length) {
+ dst |= byteArray[sourceIndex + 1] << (8 - shiftMod) & carryMask & 0xff;
+ }
+ byteArray[i] = dst;
+ }
+ }
+ }
+ return byteArray;
}
- return primitivesArray;
- }
- /**
- * Creates a byte array from given long array.
- * The resulting byte array will have length longArray * 8
- *
- * @param longArray to convert
- * @return resulting byte array
- */
- static byte[] toByteArray(long[] longArray) {
- byte[] primitivesArray = new byte[longArray.length * 8];
- for (int i = 0; i < longArray.length; i++) {
- byte[] longBytes = ByteBuffer.allocate(8).putLong(longArray[i]).array();
- System.arraycopy(longBytes, 0, primitivesArray, (i * 8), longBytes.length);
+ /**
+ * See https://codahale.com/a-lesson-in-timing-attacks/
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to check for equals
+ * @param anotherArray to check against array
+ * @return if both arrays have the same length and same length for every index
+ */
+ static boolean constantTimeEquals(byte[] array, byte[] anotherArray) {
+ if (anotherArray == null || array.length != anotherArray.length) return false;
+
+ int result = 0;
+ for (int i = 0; i < array.length; i++) {
+ result |= array[i] ^ anotherArray[i];
+ }
+ return result == 0;
}
- return primitivesArray;
- }
- /**
- * Simple Durstenfeld shuffle
- *
- * See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
- *
- * @param array
- * @param random
- */
- static void shuffle(byte[] array, Random random) {
- for (int i = array.length - 1; i > 0; i--) {
- int index = random.nextInt(i + 1);
- byte a = array[index];
- array[index] = array[i];
- array[i] = a;
+ /**
+ * Calculates the entropy factor of a byte array.
+ *
+ * This implementation will not create a copy of the internal array and will only internally initialize
+ * an int array with 256 elements as temporary buffer.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to calculate the entropy from
+ * @return entropy factor, higher means higher entropy
+ */
+ static double entropy(byte[] array) {
+ final int[] buffer = new int[256];
+ Arrays.fill(buffer, -1);
+
+ for (byte element : array) {
+ int unsigned = 0xff & element;
+ if (buffer[unsigned] == -1) {
+ buffer[unsigned] = 0;
+ }
+ buffer[unsigned]++;
+ }
+
+ double entropy = 0;
+ for (int count : buffer) {
+ if (count == -1) continue;
+ double prob = (double) count / array.length;
+ entropy -= prob * (Math.log(prob) / Math.log(2));
+ }
+ return entropy;
}
}
/**
- * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex}
- * exclusive. This is equivalent to {@code
- * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more
- * efficient.
- *
- * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or
- * {@code toIndex > fromIndex}
+ * Util method related converting byte arrays to other types.
*/
- static void reverse(byte[] array, int fromIndex, int toIndex) {
- Objects.requireNonNull(array);
- for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) {
- byte tmp = array[i];
- array[i] = array[j];
- array[j] = tmp;
+ static final class Converter {
+ private Converter() {
}
- }
- private static final int BUF_SIZE = 0x1000; // 4K
+ /**
+ * Copies a collection of {@code Byte} instances into a new array of
+ * primitive {@code byte} values.
+ *
+ *
Elements are copied from the argument collection as if by {@code
+ * collection.toArray()}. Calling this method is as thread-safe as calling
+ * that method.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param collection a collection of {@code Byte} objects
+ * @return an array containing the same values as {@code collection}, in the
+ * same order, converted to primitives
+ * @throws NullPointerException if {@code collection} or any of its elements
+ * is null
+ */
+ static byte[] toArray(Collection collection) {
+ final int len = collection.size();
+ final byte[] array = new byte[len];
+ int i = 0;
+ for (java.lang.Byte b : collection) {
+ array[i] = b;
+ i++;
+ }
+ return array;
+ }
- /**
- * Read all bytes, buffered, from given input stream
- *
- * @param inputStream to read from
- * @return all bytes from the stream
- */
- static byte[] readFromStream(InputStream inputStream) {
- try {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buf = new byte[BUF_SIZE];
- while (true) {
- int r = inputStream.read(buf);
- if (r == -1) {
- break;
+ /**
+ * Converts this primitive array to a boxed object array.
+ * Will create a new array and not reuse the array reference.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to convert
+ * @return new array
+ */
+ static java.lang.Byte[] toBoxedArray(byte[] array) {
+ java.lang.Byte[] objectArray = new java.lang.Byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ objectArray[i] = array[i];
+ }
+ return objectArray;
+ }
+
+ /**
+ * Converts given array to list of boxed bytes. Will create a new list
+ * and not reuse the array reference.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param array to convert
+ * @return list with same length and content as array
+ */
+ static List toList(byte[] array) {
+ List list = new ArrayList<>(array.length);
+ for (byte b : array) {
+ list.add(b);
+ }
+ return list;
+ }
+
+ /**
+ * Converts this object array to a primitives type array.
+ * Will create a new array and not reuse the array reference.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param objectArray to convert
+ * @return new array
+ */
+ static byte[] toPrimitiveArray(java.lang.Byte[] objectArray) {
+ byte[] primitivesArray = new byte[objectArray.length];
+ for (int i = 0; i < objectArray.length; i++) {
+ primitivesArray[i] = objectArray[i];
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Creates a byte array from given short array.
+ * The resulting byte array will have length shortArray * 2.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param shortArray to convert
+ * @return resulting byte array
+ */
+ static byte[] toByteArray(short[] shortArray) {
+ byte[] primitivesArray = new byte[shortArray.length * 2];
+ ByteBuffer buffer = ByteBuffer.allocate(2);
+ for (int i = 0; i < shortArray.length; i++) {
+ buffer.clear();
+ byte[] shortBytes = buffer.putShort(shortArray[i]).array();
+ System.arraycopy(shortBytes, 0, primitivesArray, (i * 2), shortBytes.length);
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Creates a byte array from given int array.
+ * The resulting byte array will have length intArray * 4.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param intArray to convert
+ * @return resulting byte array
+ */
+ static byte[] toByteArray(int[] intArray) {
+ byte[] primitivesArray = new byte[intArray.length * 4];
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ for (int i = 0; i < intArray.length; i++) {
+ buffer.clear();
+ byte[] intBytes = buffer.putInt(intArray[i]).array();
+ System.arraycopy(intBytes, 0, primitivesArray, (i * 4), intBytes.length);
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Creates a byte array from given float array.
+ * The resulting byte array will have length floatArray * 4.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param floatArray to convert
+ * @return resulting byte array
+ */
+ static byte[] toByteArray(float[] floatArray) {
+ byte[] primitivesArray = new byte[floatArray.length * 4];
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ for (int i = 0; i < floatArray.length; i++) {
+ buffer.clear();
+ byte[] floatBytes = buffer.putFloat(floatArray[i]).array();
+ System.arraycopy(floatBytes, 0, primitivesArray, (i * 4), floatBytes.length);
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Creates a byte array from given long array.
+ * The resulting byte array will have length longArray * 8
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param longArray to convert
+ * @return resulting byte array
+ */
+ static byte[] toByteArray(long[] longArray) {
+ byte[] primitivesArray = new byte[longArray.length * 8];
+ ByteBuffer buffer = ByteBuffer.allocate(8);
+ for (int i = 0; i < longArray.length; i++) {
+ buffer.clear();
+ byte[] longBytes = buffer.putLong(longArray[i]).array();
+ System.arraycopy(longBytes, 0, primitivesArray, (i * 8), longBytes.length);
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Creates a byte array from given double array.
+ * The resulting byte array will have length doubleArray * 8.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param doubleArray to convert
+ * @return resulting byte array
+ */
+ static byte[] toByteArray(double[] doubleArray) {
+ byte[] primitivesArray = new byte[doubleArray.length * 8];
+ ByteBuffer buffer = ByteBuffer.allocate(8);
+ for (int i = 0; i < doubleArray.length; i++) {
+ buffer.clear();
+ byte[] doubleBytes = buffer.putDouble(doubleArray[i]).array();
+ System.arraycopy(doubleBytes, 0, primitivesArray, (i * 8), doubleBytes.length);
+ }
+ return primitivesArray;
+ }
+
+ /**
+ * Converts a char array to a byte array with given charset and range
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param charArray to get the byte array from
+ * @param charset charset to be used to decode the char array
+ * @param offset to start reading the char array from (must be smaller than length and gt 0)
+ * @param length from offset (must be between 0 and charArray.length())
+ * @return byte array of encoded chars
+ */
+ static byte[] charToByteArray(char[] charArray, Charset charset, int offset, int length) {
+ if (offset < 0 || offset > charArray.length)
+ throw new IllegalArgumentException("offset must be gt 0 and smaller than array length");
+ if (length < 0 || length > charArray.length)
+ throw new IllegalArgumentException("length must be at least 1 and less than array length");
+ if (offset + length > charArray.length)
+ throw new IllegalArgumentException("length + offset must be smaller than array length");
+
+ if (length == 0) return new byte[0];
+
+ CharBuffer charBuffer = CharBuffer.wrap(charArray);
+
+ if (offset != 0 || length != charBuffer.remaining()) {
+ charBuffer = charBuffer.subSequence(offset, offset + length);
+ }
+
+ ByteBuffer bb = charset.encode(charBuffer);
+ if (bb.capacity() != bb.limit()) {
+ byte[] bytes = new byte[bb.remaining()];
+ bb.get(bytes);
+ return bytes;
+ }
+ return bb.array();
+ }
+
+ /**
+ * Convert given byte array in given encoding to char array
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param bytes as data source
+ * @param charset of the byte array
+ * @param byteOrder the order of the bytes array
+ * @return char array
+ */
+ static char[] byteToCharArray(byte[] bytes, Charset charset, ByteOrder byteOrder) {
+ Objects.requireNonNull(bytes, "bytes must not be null");
+ Objects.requireNonNull(charset, "charset must not be null");
+
+ try {
+ CharBuffer charBuffer = charset.newDecoder().decode(ByteBuffer.wrap(bytes).order(byteOrder));
+ if (charBuffer.capacity() != charBuffer.limit()) {
+ char[] compacted = new char[charBuffer.remaining()];
+ charBuffer.get(compacted);
+ return compacted;
}
- out.write(buf, 0, r);
+ return charBuffer.array();
+ } catch (CharacterCodingException e) {
+ throw new IllegalStateException(e);
}
- return out.toByteArray();
- } catch (Exception e) {
- throw new IllegalStateException("could not read from input stream", e);
}
- }
- /**
- * Read all bytes until length from given byte array.
- *
- * @param dataInput to read from
- * @return all bytes from the dataInput
- */
- static byte[] readFromDataInput(DataInput dataInput, int length) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- byte[] buf;
- int remaining = length;
- for (int i = 0; i < length; i++) {
- buf = new byte[Math.min(remaining, BUF_SIZE)];
- dataInput.readFully(buf);
- out.write(buf);
- remaining -= buf.length;
- }
- return out.toByteArray();
- } catch (Exception e) {
- throw new IllegalStateException("could not read from data input", e);
+ /**
+ * Converts the byte array to an int array. This will spread 4 bytes into a single int:
+ *
+ *
+ * [b1, b2, b3, b4] = [int1]
+ *
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param bytes to convert to int array, must be % 4 == 0 to work correctly
+ * @param byteOrder of the byte array
+ * @return int array
+ */
+ static int[] toIntArray(byte[] bytes, ByteOrder byteOrder) {
+ IntBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asIntBuffer();
+ int[] array = new int[buffer.remaining()];
+ buffer.get(array);
+ return array;
}
- }
- /**
- * Combines a single argument with a vararg to a single array
- *
- * @param firstByte first arg
- * @param moreBytes varargs
- * @return array containing all args
- */
- static byte[] concatVararg(byte firstByte, byte[] moreBytes) {
- if (moreBytes == null) {
- return new byte[]{firstByte};
- } else {
- return concat(new byte[]{firstByte}, moreBytes);
+ /**
+ * Converts the byte array to a long array. This will spread 8 bytes into a single long:
+ *
+ *
+ *
+ *
+ * @param bytes to convert to long array, must be % 8 == 0 to work correctly
+ * @param byteOrder of the byte array
+ * @return long array
+ */
+ static long[] toLongArray(byte[] bytes, ByteOrder byteOrder) {
+ LongBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asLongBuffer();
+ long[] array = new long[buffer.remaining()];
+ buffer.get(array);
+ return array;
}
- }
- /**
- * Reads all bytes from a file
- *
- * @param file the file to read
- * @return byte content
- */
- static byte[] readFromFile(File file) {
- if (file == null || !file.exists() || !file.isFile()) {
- throw new IllegalArgumentException("file must not be null, has to exist and must be a file (not a directory) " + file);
+ /**
+ * Converts the byte array to a float array. This will spread 4 bytes into a single float:
+ *
+ *
+ * [b1, b2, b3, b4] = [float1]
+ *
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param bytes to convert to float array, must be % 4 == 0 to work correctly
+ * @param byteOrder of the byte array
+ * @return float array
+ */
+ static float[] toFloatArray(byte[] bytes, ByteOrder byteOrder) {
+ FloatBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asFloatBuffer();
+ float[] array = new float[buffer.remaining()];
+ buffer.get(array);
+ return array;
}
- try {
- return Files.readAllBytes(file.toPath());
- } catch (IOException e) {
- throw new IllegalStateException("could not read from file", e);
+ /**
+ * Converts the byte array to a double array. This will spread 8 bytes into a single double:
+ *
+ *
+ *
+ *
+ * @param bytes to convert to double array, must be % 8 == 0 to work correctly
+ * @param byteOrder of the byte array
+ * @return double array
+ */
+ static double[] toDoubleArray(byte[] bytes, ByteOrder byteOrder) {
+ DoubleBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asDoubleBuffer();
+ double[] array = new double[buffer.remaining()];
+ buffer.get(array);
+ return array;
}
- }
- /**
- * Shows the length and a preview of max 8 bytes of the given byte
- *
- * @param bytes to convert to string
- * @return string representation
- */
- static String toString(Bytes bytes) {
- String preview;
- if (bytes.isEmpty()) {
- preview = "";
- } else if (bytes.length() > 8) {
- preview = "(0x" + bytes.copy(0, 4).encodeHex() + "..." + bytes.copy(bytes.length() - 4, 4).encodeHex() + ")";
- } else {
- preview = "(0x" + bytes.encodeHex() + ")";
+ /**
+ * Converts the byte array to a short array. This will spread 2 bytes into a single short:
+ *
+ *
+ * [b1, b2] = [short1]
+ *
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(n)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param bytes to convert to short array, must be % 2 == 0 to work correctly
+ * @param byteOrder of the byte array
+ * @return short array
+ */
+ static short[] toShortArray(byte[] bytes, ByteOrder byteOrder) {
+ ShortBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asShortBuffer();
+ short[] array = new short[buffer.remaining()];
+ buffer.get(array);
+ return array;
}
- return bytes.length() + " " + (bytes.length() == 1 ? "byte" : "bytes") + " " + preview;
+ /**
+ * Convert UUID to a newly generated 16 byte long array representation. Puts the 8 byte most significant bits and
+ * 8 byte least-significant bits into a byte array.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(1)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param uuid to convert to array
+ * @return buffer containing the 16 bytes
+ */
+ static ByteBuffer toBytesFromUUID(UUID uuid) {
+ ByteBuffer bb = ByteBuffer.allocate(16);
+ bb.putLong(uuid.getMostSignificantBits());
+ bb.putLong(uuid.getLeastSignificantBits());
+ return bb;
+ }
}
/**
- * Light shift of whole byte array by shiftBitCount bits.
- * This method will alter the input byte array.
+ * Util method related to Object class methods.
*/
- static byte[] shiftLeft(byte[] byteArray, int shiftBitCount) {
- final int shiftMod = shiftBitCount % 8;
- final byte carryMask = (byte) ((1 << shiftMod) - 1);
- final int offsetBytes = (shiftBitCount / 8);
-
- int sourceIndex;
- for (int i = 0; i < byteArray.length; i++) {
- sourceIndex = i + offsetBytes;
- if (sourceIndex >= byteArray.length) {
- byteArray[i] = 0;
- } else {
- byte src = byteArray[sourceIndex];
- byte dst = (byte) (src << shiftMod);
- if (sourceIndex + 1 < byteArray.length) {
- dst |= byteArray[sourceIndex + 1] >>> (8 - shiftMod) & carryMask;
+ static final class Obj {
+ private Obj() {
+ }
+
+ /**
+ * Equals method comparing 2 byte arrays.
+ * This utilizes a quick return of the array differs on any given property so not suitable
+ * for security relevant checks. See {@link Util.Byte#constantTimeEquals(byte[], byte[])}
+ * for that.
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param obj subject a
+ * @param anotherArray subject b to compare to a
+ * @return if a.len == b.len and for every 0..len a[i] == b[i]
+ */
+ static boolean equals(byte[] obj, java.lang.Byte[] anotherArray) {
+ if (anotherArray == null) return false;
+ if (obj.length != anotherArray.length) return false;
+ for (int i = 0; i < obj.length; i++) {
+ if (anotherArray[i] == null || obj[i] != anotherArray[i]) {
+ return false;
}
- byteArray[i] = dst;
}
+ return true;
}
- return byteArray;
- }
- /**
- * Unsigned/logical right shift of whole byte array by shiftBitCount bits.
- * This method will alter the input byte array.
- */
- static byte[] shiftRight(byte[] byteArray, int shiftBitCount) {
- final int shiftMod = shiftBitCount % 8;
- final byte carryMask = (byte) (0xFF << (8 - shiftMod));
- final int offsetBytes = (shiftBitCount / 8);
-
- int sourceIndex;
- for (int i = byteArray.length - 1; i >= 0; i--) {
- sourceIndex = i - offsetBytes;
- if (sourceIndex < 0) {
- byteArray[i] = 0;
+ /**
+ * Hashcode implementation for a byte array and given byte order
+ *
+ *
+ * Analysis
+ *
+ *
Time Complexity: O(n)
+ *
Space Complexity: O(1)
+ *
Alters Parameters: false
+ *
+ *
+ *
+ * @param byteArray to calculate hashCode of
+ * @param byteOrder to calculate hashCode of
+ * @return hashCode
+ */
+ static int hashCode(byte[] byteArray, ByteOrder byteOrder) {
+ int result = Arrays.hashCode(byteArray);
+ result = 31 * result + (byteOrder != null ? byteOrder.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * Shows the length and a preview of max 8 bytes of the given byte
+ *
+ *
+ * REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
+ * why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
+ * experiments, perform baseline and negative tests that provide experimental control, make sure
+ * the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
+ * Do not assume the numbers tell you what you want them to tell.
+ *