SecureHashable.java
/*
* Copyright 2020 Collect Utils Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.collect;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* A type that can be hashed to a secure 64 bit (long) hash.
*/
public interface SecureHashable {
/**
* Get a secure hash code for the value. This is meant to be used on
* values that can have pretty similar values, not only to ensure unique
* hash codes, but also numerical distribution among the 64-bit integers.
* <p>
* The
*
* @return A secure hash code.
*/
long secureHashCode();
/**
* Make a secure hash code from an object.
*
* @param o The object to hash.
* @return The secure hashcode.
*/
static long secureHash(Object o) {
if (o instanceof SecureHashable) {
return ((SecureHashable) o).secureHashCode();
} else if (o instanceof CharSequence) {
return secureHash((CharSequence) o);
} else if (o instanceof byte[]) {
return secureHash((byte[]) o);
} else if (o instanceof Set) {
long result = -1466376081728027815L;
result += (5345617L + ((Set<?>) o).size()) * 2993991083002160833L;
for (var i : (Set<?>) o) {
// The order does not matter in sets.
result += secureHash(i) * 8778220231891916799L;
}
return result;
} else if (o instanceof Collection) {
long result = -1466376081728027815L;
for (var i : (Collection<?>) o) {
// The order matters in other collections.
result *= -2303118536797078809L;
result += secureHash(i) * 8778220231891916799L;
}
return result;
} else if (o instanceof Map) {
long result = 2993991083002160833L;
result += (5345617L + ((Map<?, ?>) o).size()) * -1466376081728027815L;
for (var e : ((Map<?, ?>) o).entrySet()) {
result += (7675589L + secureHash(e.getKey())) *
(96545101L + secureHash(e.getValue())) *
150090999566823819L;
}
return result;
}
return 2113876914436862725L * (5345617L + o.hashCode());
}
/**
* Make a secure hash code from a char sequence or string.
*
* @param sequence The string to hash.
* @return The secure hashcode.
*/
static long secureHash(CharSequence sequence) {
long result = 6965299255647741657L;
for (int pos = 0; pos < sequence.length(); ++pos) {
result *= -2113876914436862725L;
result += (5345617L + sequence.charAt(pos)) * -7735678928159363275L;
}
return result;
}
/**
* Make a secure hash code from a byte array.
*
* @param array The array to hash.
* @return The secure hashcode.
*/
static long secureHash(byte[] array) {
return secureHash(array, 0, array.length);
}
/**
* Make a secure hash code from a byte array.
*
* @param array The array to hash.
* @param offset Offset to start hashing at.
* @param length Number of bytes to hash.
* @return The secure hashcode.
*/
static long secureHash(byte[] array, int offset, int length) {
long result = 8812079937318930253L;
for (int i = 0; i < length; ++i) {
result *= 3553555017746745835L;
result += (344629L + array[i + offset]) * 8670115223866369197L;
}
return result;
}
}