I have a legacy identifier with a reference implementation is JS. I need to generate IDs in Java.
I rarely have to think about bitwise operations, so this problem started out frustrating, but I developed an appreciation for the elegance of binary, hex and bitwise operations as I warmed up, and was reluctant to trade them in for the relatively self-documenting Java syntax at the end.
We use an array of 8-bit, unsigned ints:
var ints = new Uint8Array(8)
Presumably, we’re using unsigned ints so we can encode
We populate this array with random values:
We get the last (random) 4 bits of the first byte using the AND operator against 1111 (0xF in hex):
var rand = 0xF & ints
We prepend 0111 by adding 011100000 (0x70 in hex):
ints = 0x70 + rand
I think this may be due to max long in Java, ie
Long.MAX_VALUE == 0x7FFF_FFFF_FFFF_FFFFL.
We convert this to an array of chars using and then base64 encode:
I think the base64 encoding is to retain the integrity of the “bytes”.
> atob("ekMJT1sVNBc=").split('').map(c=>c.charCodeAt(0)) < [122, 67, 9, 79, 91, 21, 52, 23]
Java makes this easier with native 64 bit long, byte primitives and convenience classes. Java REPL makes it easy to hack around.
For sanity, can we decode the value produced by JS?
import java.nio.ByteBuffer import java.util.Base64 String encoded = "ekMJT1sVNBc=" byte bytes = Base64.getDecoder().decode(encoded) long id = ByteBuffer.wrap(bytes).getLong()
Generate a random long within a range 0x7…:
import java.util.Random long id = new Random().longs(0x7000_0000_0000_0000L, Long.MAX_VALUE).limit(1).findFirst().getAsLong()
Convert to bytes:
byte bytes = ByteBuffer.allocate(Long.BYTES).putLong(id).array()
Note this method produces values outside the range 0-255, eg:
< byte bytes = [116, 91, -54, -35, -92, -103, 127, -28] > Integer.toHexString(bytes) < "ffffffca"
I suspect ID generation originated in Java as random unsigned longs, was replicated in JS