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 019package org.apache.hadoop.io; 020 021import java.io.IOException; 022import java.io.DataInput; 023import java.io.DataOutput; 024import java.io.InputStream; 025import java.util.Arrays; 026import java.security.*; 027 028import org.apache.hadoop.classification.InterfaceAudience; 029import org.apache.hadoop.classification.InterfaceStability; 030 031/** A Writable for MD5 hash values. 032 */ 033@InterfaceAudience.Public 034@InterfaceStability.Stable 035public 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}