1- /**
1+ /*
22 * Licensed to the Apache Software Foundation (ASF) under one
33 * or more contributor license agreements. See the NOTICE file
44 * distributed with this work for additional information
1818
1919package org .apache .ratis .io ;
2020
21- import java .io .DataInput ;
22- import java .io .DataOutput ;
23- import java .io .IOException ;
24- import java .io .InputStream ;
25- import java .security .MessageDigest ;
26- import java .security .NoSuchAlgorithmException ;
27- import java .util .Arrays ;
28-
29- public class MD5Hash {
30- public static final int MD5_LEN = 16 ;
31-
32- private static final ThreadLocal <MessageDigest > DIGESTER_FACTORY =
33- ThreadLocal .withInitial (MD5Hash ::newDigester );
34-
35- public static MessageDigest newDigester () {
36- try {
37- return MessageDigest .getInstance ("MD5" );
38- } catch (NoSuchAlgorithmException e ) {
39- throw new IllegalStateException ("Failed to create MessageDigest for MD5" , e );
40- }
41- }
42-
43- private byte [] digest ;
44-
45- /** Constructs an MD5Hash. */
46- public MD5Hash () {
47- this .digest = new byte [MD5_LEN ];
48- }
49-
50- /** Constructs an MD5Hash from a hex string. */
51- public MD5Hash (String hex ) {
52- setDigest (hex );
53- }
54-
55- /** Constructs an MD5Hash with a specified value. */
56- public MD5Hash (byte [] digest ) {
57- if (digest .length != MD5_LEN ) {
58- throw new IllegalArgumentException ("Wrong length: " + digest .length );
59- }
60- this .digest = digest .clone ();
61- }
62-
63- public void readFields (DataInput in ) throws IOException {
64- in .readFully (digest );
65- }
66-
67- /** Constructs, reads and returns an instance. */
68- public static MD5Hash read (DataInput in ) throws IOException {
69- MD5Hash result = new MD5Hash ();
70- result .readFields (in );
71- return result ;
72- }
73-
74- public void write (DataOutput out ) throws IOException {
75- out .write (digest );
76- }
77-
78- /** Copy the contents of another instance into this instance. */
79- public void set (MD5Hash that ) {
80- System .arraycopy (that .digest , 0 , this .digest , 0 , MD5_LEN );
81- }
82-
83- /** Returns the digest bytes. */
84- public byte [] getDigest () {
85- return digest .clone ();
86- }
87-
88- /** Construct a hash value for a byte array. */
89- public static MD5Hash digest (byte [] data ) {
90- return digest (data , 0 , data .length );
91- }
92-
93- /**
94- * Create a thread local MD5 digester
95- */
96- public static MessageDigest getDigester () {
97- MessageDigest digester = DIGESTER_FACTORY .get ();
98- digester .reset ();
99- return digester ;
100- }
21+ import org .apache .ratis .util .MemoizedSupplier ;
22+ import org .apache .ratis .util .Preconditions ;
10123
102- /** Construct a hash value for the content from the InputStream. */
103- public static MD5Hash digest (InputStream in ) throws IOException {
104- final byte [] buffer = new byte [4 *1024 ];
24+ import java .nio .ByteBuffer ;
25+ import java .util .Arrays ;
26+ import java .util .Objects ;
27+ import java .util .function .Supplier ;
10528
106- final MessageDigest digester = getDigester ();
107- for (int n ; (n = in .read (buffer )) != -1 ; ) {
108- digester .update (buffer , 0 , n );
29+ /**
30+ * A MD5 hash value.
31+ * <p>
32+ * This is a value-based class.
33+ */
34+ public final class MD5Hash {
35+ public static final int MD5_LENGTH = 16 ;
36+
37+ /** @return an instance with the given digest in a (case-insensitive) hexadecimals. */
38+ public static MD5Hash newInstance (String digestHexadecimals ) {
39+ Objects .requireNonNull (digestHexadecimals , "digestHexadecimals == null" );
40+ Preconditions .assertSame (2 * MD5_LENGTH , digestHexadecimals .length (), "digestHexadecimals" );
41+
42+ final byte [] digest = new byte [MD5_LENGTH ];
43+ for (int i = 0 ; i < MD5_LENGTH ; i ++) {
44+ final int j = i << 1 ;
45+ digest [i ] = (byte ) (charToNibble (digestHexadecimals , j ) << 4 |
46+ charToNibble (digestHexadecimals , j + 1 ));
10947 }
110-
111- return new MD5Hash (digester .digest ());
112- }
113-
114- /** Construct a hash value for a byte array. */
115- public static MD5Hash digest (byte [] data , int start , int len ) {
116- byte [] digest ;
117- MessageDigest digester = getDigester ();
118- digester .update (data , start , len );
119- digest = digester .digest ();
12048 return new MD5Hash (digest );
12149 }
12250
123- /** Construct a hash value for an array of byte array. */
124- public static MD5Hash digest (byte [][] dataArr , int start , int len ) {
125- byte [] digest ;
126- MessageDigest digester = getDigester ();
127- for (byte [] data : dataArr ) {
128- digester .update (data , start , len );
129- }
130- digest = digester .digest ();
131- return new MD5Hash (digest );
51+ /** @return an instance with the given digest. */
52+ public static MD5Hash newInstance (byte [] digest ) {
53+ Objects .requireNonNull (digest , "digest == null" );
54+ Preconditions .assertSame (MD5_LENGTH , digest .length , "digest" );
55+ return new MD5Hash (digest .clone ());
13256 }
13357
134- /** Construct a half-sized version of this MD5. Fits in a long **/
135- public long halfDigest () {
136- long value = 0 ;
137- for (int i = 0 ; i < 8 ; i ++) {
138- value |= ((digest [i ] & 0xffL ) << (8 *(7 -i )));
139- }
140- return value ;
58+ private final byte [] digest ;
59+ private final Supplier <String > digestString ;
60+
61+ private MD5Hash (byte [] digest ) {
62+ this .digest = digest ;
63+ this .digestString = MemoizedSupplier .valueOf (() -> digestToString (digest ));
14164 }
14265
143- /**
144- * Return a 32-bit digest of the MD5.
145- * @return the first 4 bytes of the md5
146- */
147- public int quarterDigest () {
148- int value = 0 ;
149- for (int i = 0 ; i < 4 ; i ++) {
150- value |= ((digest [i ] & 0xff ) << (8 *(3 -i )));
151- }
152- return value ;
66+ /** @return the digest wrapped by a read-only {@link ByteBuffer}. */
67+ public ByteBuffer getDigest () {
68+ return ByteBuffer .wrap (digest ).asReadOnlyBuffer ();
15369 }
15470
155- /** Returns true iff <code>o</code> is an MD5Hash whose digest contains the
156- * same values. */
15771 @ Override
158- public boolean equals (Object o ) {
159- if (!(o instanceof MD5Hash )) {
72+ public boolean equals (Object object ) {
73+ if (this == object ) {
74+ return true ;
75+ } else if (!(object instanceof MD5Hash )) {
16076 return false ;
16177 }
162- MD5Hash other = (MD5Hash )o ;
163- return Arrays .equals (this .digest , other .digest );
78+ final MD5Hash that = (MD5Hash ) object ;
79+ return Arrays .equals (this .digest , that .digest );
16480 }
16581
166- /** Returns a hash code value for this object.
167- * Only uses the first 4 bytes, since md5s are evenly distributed.
168- */
16982 @ Override
17083 public int hashCode () {
171- return quarterDigest ();
84+ return ((digest [0 ] & 0xFF ) << 24 )
85+ | ((digest [1 ] & 0xFF ) << 16 )
86+ | ((digest [2 ] & 0xFF ) << 8 )
87+ | (digest [3 ] & 0xFF );
17288 }
17389
17490 private static final char [] HEX_DIGITS =
@@ -177,37 +93,30 @@ public int hashCode() {
17793 /** Returns a string representation of this object. */
17894 @ Override
17995 public String toString () {
180- StringBuilder buf = new StringBuilder (MD5_LEN *2 );
181- for (int i = 0 ; i < MD5_LEN ; i ++) {
96+ return digestString .get ();
97+ }
98+
99+ static String digestToString (byte [] digest ) {
100+ StringBuilder buf = new StringBuilder (MD5_LENGTH *2 );
101+ for (int i = 0 ; i < MD5_LENGTH ; i ++) {
182102 int b = digest [i ];
183103 buf .append (HEX_DIGITS [(b >> 4 ) & 0xf ]);
184104 buf .append (HEX_DIGITS [b & 0xf ]);
185105 }
186106 return buf .toString ();
187107 }
188108
189- /** Sets the digest value from a hex string. */
190- public void setDigest (String hex ) {
191- if (hex .length () != MD5_LEN *2 ) {
192- throw new IllegalArgumentException ("Wrong length: " + hex .length ());
193- }
194- this .digest = new byte [MD5_LEN ];
195- for (int i = 0 ; i < MD5_LEN ; i ++) {
196- int j = i << 1 ;
197- this .digest [i ] = (byte )(charToNibble (hex .charAt (j )) << 4 |
198- charToNibble (hex .charAt (j +1 )));
199- }
200- }
201-
202- private static int charToNibble (char c ) {
109+ private static int charToNibble (String hexadecimals , int i ) {
110+ final char c = hexadecimals .charAt (i );
203111 if (c >= '0' && c <= '9' ) {
204112 return c - '0' ;
205113 } else if (c >= 'a' && c <= 'f' ) {
206114 return 0xa + (c - 'a' );
207115 } else if (c >= 'A' && c <= 'F' ) {
208116 return 0xA + (c - 'A' );
209117 } else {
210- throw new RuntimeException ("Not a hex character: " + c );
118+ throw new IllegalArgumentException (
119+ "Found a non-hexadecimal character '" + c + "' at index " + i + " in \" " + hexadecimals + "\" " );
211120 }
212121 }
213122}
0 commit comments