001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.apache.hadoop.io;
020
021 import java.io.IOException;
022 import java.io.DataInput;
023 import java.io.DataOutput;
024 import java.io.InputStream;
025 import java.util.Arrays;
026 import java.security.*;
027
028 import org.apache.hadoop.classification.InterfaceAudience;
029 import org.apache.hadoop.classification.InterfaceStability;
030
031 /** A Writable for MD5 hash values.
032 */
033 @InterfaceAudience.Public
034 @InterfaceStability.Stable
035 public class MD5Hash implements WritableComparable<MD5Hash> {
036 public static final int MD5_LEN = 16;
037
038 private static ThreadLocal<MessageDigest> DIGESTER_FACTORY = new ThreadLocal<MessageDigest>() {
039 protected MessageDigest initialValue() {
040 try {
041 return MessageDigest.getInstance("MD5");
042 } catch (NoSuchAlgorithmException e) {
043 throw new RuntimeException(e);
044 }
045 }
046 };
047
048 private byte[] digest;
049
050 /** Constructs an MD5Hash. */
051 public MD5Hash() {
052 this.digest = new byte[MD5_LEN];
053 }
054
055 /** Constructs an MD5Hash from a hex string. */
056 public MD5Hash(String hex) {
057 setDigest(hex);
058 }
059
060 /** Constructs an MD5Hash with a specified value. */
061 public MD5Hash(byte[] digest) {
062 if (digest.length != MD5_LEN)
063 throw new IllegalArgumentException("Wrong length: " + digest.length);
064 this.digest = digest;
065 }
066
067 // javadoc from Writable
068 public void readFields(DataInput in) throws IOException {
069 in.readFully(digest);
070 }
071
072 /** Constructs, reads and returns an instance. */
073 public static MD5Hash read(DataInput in) throws IOException {
074 MD5Hash result = new MD5Hash();
075 result.readFields(in);
076 return result;
077 }
078
079 // javadoc from Writable
080 public void write(DataOutput out) throws IOException {
081 out.write(digest);
082 }
083
084 /** Copy the contents of another instance into this instance. */
085 public void set(MD5Hash that) {
086 System.arraycopy(that.digest, 0, this.digest, 0, MD5_LEN);
087 }
088
089 /** Returns the digest bytes. */
090 public byte[] getDigest() { return digest; }
091
092 /** Construct a hash value for a byte array. */
093 public static MD5Hash digest(byte[] data) {
094 return digest(data, 0, data.length);
095 }
096
097 /**
098 * Create a thread local MD5 digester
099 */
100 public static MessageDigest getDigester() {
101 MessageDigest digester = DIGESTER_FACTORY.get();
102 digester.reset();
103 return digester;
104 }
105
106 /** Construct a hash value for the content from the InputStream. */
107 public static MD5Hash digest(InputStream in) throws IOException {
108 final byte[] buffer = new byte[4*1024];
109
110 final MessageDigest digester = getDigester();
111 for(int n; (n = in.read(buffer)) != -1; ) {
112 digester.update(buffer, 0, n);
113 }
114
115 return new MD5Hash(digester.digest());
116 }
117
118 /** Construct a hash value for a byte array. */
119 public static MD5Hash digest(byte[] data, int start, int len) {
120 byte[] digest;
121 MessageDigest digester = getDigester();
122 digester.update(data, start, len);
123 digest = digester.digest();
124 return new MD5Hash(digest);
125 }
126
127 /** Construct a hash value for a String. */
128 public static MD5Hash digest(String string) {
129 return digest(UTF8.getBytes(string));
130 }
131
132 /** Construct a hash value for a String. */
133 public static MD5Hash digest(UTF8 utf8) {
134 return digest(utf8.getBytes(), 0, utf8.getLength());
135 }
136
137 /** Construct a half-sized version of this MD5. Fits in a long **/
138 public long halfDigest() {
139 long value = 0;
140 for (int i = 0; i < 8; i++)
141 value |= ((digest[i] & 0xffL) << (8*(7-i)));
142 return value;
143 }
144
145 /**
146 * Return a 32-bit digest of the MD5.
147 * @return the first 4 bytes of the md5
148 */
149 public int quarterDigest() {
150 int value = 0;
151 for (int i = 0; i < 4; i++)
152 value |= ((digest[i] & 0xff) << (8*(3-i)));
153 return value;
154 }
155
156 /** Returns true iff <code>o</code> is an MD5Hash whose digest contains the
157 * same values. */
158 public boolean equals(Object o) {
159 if (!(o instanceof MD5Hash))
160 return false;
161 MD5Hash other = (MD5Hash)o;
162 return Arrays.equals(this.digest, other.digest);
163 }
164
165 /** Returns a hash code value for this object.
166 * Only uses the first 4 bytes, since md5s are evenly distributed.
167 */
168 public int hashCode() {
169 return quarterDigest();
170 }
171
172
173 /** Compares this object with the specified object for order.*/
174 public int compareTo(MD5Hash that) {
175 return WritableComparator.compareBytes(this.digest, 0, MD5_LEN,
176 that.digest, 0, MD5_LEN);
177 }
178
179 /** A WritableComparator optimized for MD5Hash keys. */
180 public static class Comparator extends WritableComparator {
181 public Comparator() {
182 super(MD5Hash.class);
183 }
184
185 public int compare(byte[] b1, int s1, int l1,
186 byte[] b2, int s2, int l2) {
187 return compareBytes(b1, s1, MD5_LEN, b2, s2, MD5_LEN);
188 }
189 }
190
191 static { // register this comparator
192 WritableComparator.define(MD5Hash.class, new Comparator());
193 }
194
195 private static final char[] HEX_DIGITS =
196 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
197
198 /** Returns a string representation of this object. */
199 public String toString() {
200 StringBuilder buf = new StringBuilder(MD5_LEN*2);
201 for (int i = 0; i < MD5_LEN; i++) {
202 int b = digest[i];
203 buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
204 buf.append(HEX_DIGITS[b & 0xf]);
205 }
206 return buf.toString();
207 }
208
209 /** Sets the digest value from a hex string. */
210 public void setDigest(String hex) {
211 if (hex.length() != MD5_LEN*2)
212 throw new IllegalArgumentException("Wrong length: " + hex.length());
213 byte[] digest = new byte[MD5_LEN];
214 for (int i = 0; i < MD5_LEN; i++) {
215 int j = i << 1;
216 digest[i] = (byte)(charToNibble(hex.charAt(j)) << 4 |
217 charToNibble(hex.charAt(j+1)));
218 }
219 this.digest = digest;
220 }
221
222 private static final int charToNibble(char c) {
223 if (c >= '0' && c <= '9') {
224 return c - '0';
225 } else if (c >= 'a' && c <= 'f') {
226 return 0xa + (c - 'a');
227 } else if (c >= 'A' && c <= 'F') {
228 return 0xA + (c - 'A');
229 } else {
230 throw new RuntimeException("Not a hex character: " + c);
231 }
232 }
233
234
235 }