1717 */
1818package org .jackhuang .hmcl .util ;
1919
20+ import org .jetbrains .annotations .NotNullByDefault ;
21+
2022import java .nio .charset .StandardCharsets ;
23+ import java .util .Objects ;
24+ import java .util .zip .Checksum ;
2125
2226/**
2327 * Implementation of the MurmurHash2 32-bit and 64-bit hash functions.
4852 * Original MurmurHash2 c++ code</a>
4953 * @since 1.13
5054 */
55+ @ NotNullByDefault
5156public final class MurmurHash2 {
5257
5358 // Constants for 32-bit variant
@@ -64,6 +69,177 @@ public final class MurmurHash2 {
6469 private MurmurHash2 () {
6570 }
6671
72+ /// Creates a streaming MurmurHash2 32-bit checksum for exactly `length` bytes.
73+ ///
74+ /// The length is incorporated into the initial hash state using its low 32 bits. Calls to
75+ /// [Checksum#update(int)] and [Checksum#update(byte[], int, int)] may divide the input at
76+ /// arbitrary byte boundaries. [Checksum#getValue()] returns the hash as an unsigned 32-bit
77+ /// value represented by a `long`, and does not change the checksum state.
78+ ///
79+ /// The returned checksum verifies the number of supplied bytes when `getValue()` is called.
80+ /// If too few bytes have been supplied, the caller may continue updating the checksum and call
81+ /// `getValue()` again. Once too many bytes have been supplied, [Checksum#reset()] must be called
82+ /// before a value can be obtained. Resetting retains the configured length and seed. The
83+ /// returned checksum is mutable and is not safe for concurrent use.
84+ ///
85+ /// @param length the exact number of input bytes
86+ /// @param seed the initial seed value
87+ /// @return a new checksum initialized with the specified length and seed
88+ /// @throws IllegalArgumentException if `length` is negative
89+ public static Checksum hash32 (final long length , final int seed ) {
90+ if (length < 0 ) {
91+ throw new IllegalArgumentException ("length must not be negative: " + length );
92+ }
93+ return new Hash32Checksum (length , seed );
94+ }
95+
96+ /// Computes a MurmurHash2 32-bit value from updates whose total length is known in advance.
97+ private static final class Hash32Checksum implements Checksum {
98+ /// The exact number of bytes required before the hash can be obtained.
99+ private final long expectedLength ;
100+
101+ /// The hash state restored by [#reset()].
102+ private final int initialHash ;
103+
104+ /// The hash state after all complete four-byte blocks received so far.
105+ private int hash ;
106+
107+ /// The number of bytes counted before [#inputLengthExceeded] becomes `true`.
108+ private long inputLength ;
109+
110+ /// Whether more than [#expectedLength] bytes have been received.
111+ private boolean inputLengthExceeded ;
112+
113+ /// Up to three unprocessed bytes packed in little-endian order.
114+ private int tail ;
115+
116+ /// The number of bytes currently stored in [#tail].
117+ private int tailLength ;
118+
119+ /// Creates a checksum with a precomputed initial hash state.
120+ ///
121+ /// @param expectedLength the exact number of input bytes
122+ /// @param seed the initial seed value
123+ private Hash32Checksum (long expectedLength , int seed ) {
124+ this .expectedLength = expectedLength ;
125+ this .initialHash = seed ^ (int ) expectedLength ;
126+ this .hash = initialHash ;
127+ }
128+
129+ /// Incorporates the low eight bits of `value` into this checksum.
130+ ///
131+ /// @param value the value whose low eight bits are incorporated
132+ @ Override
133+ public void update (int value ) {
134+ addInputLength (1 );
135+ appendByte (value );
136+ }
137+
138+ /// Incorporates `length` bytes beginning at `offset` into this checksum.
139+ ///
140+ /// @param data the array containing the input bytes
141+ /// @param offset the offset of the first input byte
142+ /// @param length the number of bytes to incorporate
143+ @ Override
144+ public void update (byte [] data , int offset , int length ) {
145+ Objects .checkFromIndexSize (offset , length , data .length );
146+ addInputLength (length );
147+
148+ int index = offset ;
149+ final int end = offset + length ;
150+
151+ while (tailLength != 0 && index < end ) {
152+ appendByte (data [index ++]);
153+ }
154+
155+ while (index <= end - Integer .BYTES ) {
156+ mixBlock (ByteArray .getIntLE (data , index ));
157+ index += Integer .BYTES ;
158+ }
159+
160+ while (index < end ) {
161+ appendByte (data [index ++]);
162+ }
163+ }
164+
165+ /// Returns the MurmurHash2 value after verifying the exact input length.
166+ ///
167+ /// @return the unsigned 32-bit hash value represented by a `long`
168+ /// @throws IllegalStateException if the number of supplied bytes differs from the expected
169+ /// length
170+ @ Override
171+ public long getValue () {
172+ if (inputLengthExceeded ) {
173+ throw new IllegalStateException (
174+ "Expected " + expectedLength + " bytes, but received more than expected" );
175+ }
176+ if (inputLength != expectedLength ) {
177+ throw new IllegalStateException (
178+ "Expected " + expectedLength + " bytes, but received " + inputLength );
179+ }
180+
181+ int result = hash ;
182+ if (tailLength != 0 ) {
183+ result ^= tail ;
184+ result *= M32 ;
185+ }
186+
187+ result ^= result >>> 13 ;
188+ result *= M32 ;
189+ result ^= result >>> 15 ;
190+ return Integer .toUnsignedLong (result );
191+ }
192+
193+ /// Restores this checksum to its initial state while retaining its expected length and seed.
194+ @ Override
195+ public void reset () {
196+ hash = initialHash ;
197+ inputLength = 0 ;
198+ inputLengthExceeded = false ;
199+ tail = 0 ;
200+ tailLength = 0 ;
201+ }
202+
203+ /// Records that `length` more input bytes have been supplied.
204+ ///
205+ /// @param length the non-negative number of additional bytes
206+ private void addInputLength (int length ) {
207+ if (inputLengthExceeded ) {
208+ return ;
209+ }
210+ if (length > expectedLength - inputLength ) {
211+ inputLengthExceeded = true ;
212+ } else {
213+ inputLength += length ;
214+ }
215+ }
216+
217+ /// Buffers one byte and mixes the resulting block when four bytes are available.
218+ ///
219+ /// @param value the value whose low eight bits are appended
220+ private void appendByte (int value ) {
221+ tail |= (value & 0xff ) << (tailLength * Byte .SIZE );
222+ tailLength ++;
223+ if (tailLength == Integer .BYTES ) {
224+ mixBlock (tail );
225+ tail = 0 ;
226+ tailLength = 0 ;
227+ }
228+ }
229+
230+ /// Mixes one little-endian four-byte block into the current hash state.
231+ ///
232+ /// @param block the block to mix
233+ private void mixBlock (int block ) {
234+ int mixedBlock = block ;
235+ mixedBlock *= M32 ;
236+ mixedBlock ^= mixedBlock >>> R32 ;
237+ mixedBlock *= M32 ;
238+ hash *= M32 ;
239+ hash ^= mixedBlock ;
240+ }
241+ }
242+
67243 /**
68244 * Generates a 32-bit hash from byte array with the given length and seed.
69245 *
0 commit comments