package com.google.common.hash;
import static com.google.common.primitives.UnsignedBytes.toInt;
import com.google.errorprone.annotations.Immutable;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.checkerframework.checker.nullness.qual.Nullable;
@Immutable
final class Murmur3_128HashFunction extends AbstractHashFunction implements Serializable {
static final HashFunction MURMUR3_128 = new Murmur3_128HashFunction(0);
static final HashFunction GOOD_FAST_HASH_128 =
new Murmur3_128HashFunction(Hashing.GOOD_FAST_HASH_SEED);
private final int seed;
Murmur3_128HashFunction(int seed) {
this.seed = seed;
}
@Override
public int bits() {
return 128;
}
@Override
public Hasher newHasher() {
return new Murmur3_128Hasher(seed);
}
@Override
public String toString() {
return "Hashing.murmur3_128(" + seed + ")";
}
@Override
public boolean equals(@Nullable Object object) {
if (object instanceof Murmur3_128HashFunction) {
Murmur3_128HashFunction other = (Murmur3_128HashFunction) object;
return seed == other.seed;
}
return false;
}
@Override
public int hashCode() {
return getClass().hashCode() ^ seed;
}
private static final class Murmur3_128Hasher extends AbstractStreamingHasher {
private static final int CHUNK_SIZE = 16;
private static final long C1 = 0x87c37b91114253d5L;
private static final long C2 = 0x4cf5ad432745937fL;
private long h1;
private long h2;
private int length;
Murmur3_128Hasher(int seed) {
super(CHUNK_SIZE);
this.h1 = seed;
this.h2 = seed;
this.length = 0;
}
@Override
protected void process(ByteBuffer bb) {
long k1 = bb.getLong();
long k2 = bb.getLong();
bmix64(k1, k2);
length += CHUNK_SIZE;
}
private void bmix64(long k1, long k2) {
h1 ^= mixK1(k1);
h1 = Long.rotateLeft(h1, 27);
h1 += h2;
h1 = h1 * 5 + 0x52dce729;
h2 ^= mixK2(k2);
h2 = Long.rotateLeft(h2, 31);
h2 += h1;
h2 = h2 * 5 + 0x38495ab5;
}
@Override
protected void processRemaining(ByteBuffer bb) {
long k1 = 0;
long k2 = 0;
length += bb.remaining();
switch (bb.remaining()) {
case 15:
k2 ^= (long) toInt(bb.get(14)) << 48;
case 14:
k2 ^= (long) toInt(bb.get(13)) << 40;
case 13:
k2 ^= (long) toInt(bb.get(12)) << 32;
case 12:
k2 ^= (long) toInt(bb.get(11)) << 24;
case 11:
k2 ^= (long) toInt(bb.get(10)) << 16;
case 10:
k2 ^= (long) toInt(bb.get(9)) << 8;
case 9:
k2 ^= (long) toInt(bb.get(8));
case 8:
k1 ^= bb.getLong();
break;
case 7:
k1 ^= (long) toInt(bb.get(6)) << 48;
case 6:
k1 ^= (long) toInt(bb.get(5)) << 40;
case 5:
k1 ^= (long) toInt(bb.get(4)) << 32;
case 4:
k1 ^= (long) toInt(bb.get(3)) << 24;
case 3:
k1 ^= (long) toInt(bb.get(2)) << 16;
case 2:
k1 ^= (long) toInt(bb.get(1)) << 8;
case 1:
k1 ^= (long) toInt(bb.get(0));
break;
default:
throw new AssertionError("Should never get here.");
}
h1 ^= mixK1(k1);
h2 ^= mixK2(k2);
}
@Override
public HashCode makeHash() {
h1 ^= length;
h2 ^= length;
h1 += h2;
h2 += h1;
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 += h2;
h2 += h1;
return HashCode.fromBytesNoCopy(
ByteBuffer.wrap(new byte[CHUNK_SIZE])
.order(ByteOrder.LITTLE_ENDIAN)
.putLong(h1)
.putLong(h2)
.array());
}
private static long fmix64(long k) {
k ^= k >>> 33;
k *= 0xff51afd7ed558ccdL;
k ^= k >>> 33;
k *= 0xc4ceb9fe1a85ec53L;
k ^= k >>> 33;
return k;
}
private static long mixK1(long k1) {
k1 *= C1;
k1 = Long.rotateLeft(k1, 31);
k1 *= C2;
return k1;
}
private static long mixK2(long k2) {
k2 *= C2;
k2 = Long.rotateLeft(k2, 33);
k2 *= C1;
return k2;
}
}
private static final long serialVersionUID = 0L;
}