1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package com.google.common.hash;
16
17import static com.google.common.base.Preconditions.checkState;
18import static com.google.common.base.Preconditions.checkPositionIndexes;
19
20import com.google.common.primitives.Chars;
21import com.google.common.primitives.Ints;
22import com.google.common.primitives.Longs;
23import com.google.common.primitives.Shorts;
24
25import java.nio.ByteBuffer;
26import java.nio.ByteOrder;
27import java.nio.charset.Charset;
28import java.security.MessageDigest;
29import java.security.NoSuchAlgorithmException;
30
31/**
32 * {@link HashFunction} adapter for {@link MessageDigest}s.
33 *
34 * @author kevinb@google.com (Kevin Bourrillion)
35 * @author andreou@google.com (Dimitris Andreou)
36 */
37final class MessageDigestHashFunction extends AbstractStreamingHashFunction {
38  private final String algorithmName;
39  private final int bits;
40
41  MessageDigestHashFunction(String algorithmName) {
42    this.algorithmName = algorithmName;
43    this.bits = getMessageDigest(algorithmName).getDigestLength() * 8;
44  }
45
46  public int bits() {
47    return bits;
48  }
49
50  private static MessageDigest getMessageDigest(String algorithmName) {
51    try {
52      return MessageDigest.getInstance(algorithmName);
53    } catch (NoSuchAlgorithmException e) {
54      throw new AssertionError(e);
55    }
56  }
57
58  @Override public Hasher newHasher() {
59    return new MessageDigestHasher(getMessageDigest(algorithmName));
60  }
61
62  private static class MessageDigestHasher implements Hasher {
63    private final MessageDigest digest;
64    private final ByteBuffer scratch; // lazy convenience
65    private boolean done;
66
67    private MessageDigestHasher(MessageDigest digest) {
68      this.digest = digest;
69      this.scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
70    }
71
72    @Override public Hasher putByte(byte b) {
73      checkNotDone();
74      digest.update(b);
75      return this;
76    }
77
78    @Override public Hasher putBytes(byte[] bytes) {
79      checkNotDone();
80      digest.update(bytes);
81      return this;
82    }
83
84    @Override public Hasher putBytes(byte[] bytes, int off, int len) {
85      checkNotDone();
86      checkPositionIndexes(off, off + len, bytes.length);
87      digest.update(bytes, off, len);
88      return this;
89    }
90
91    @Override public Hasher putShort(short s) {
92      checkNotDone();
93      scratch.putShort(s);
94      digest.update(scratch.array(), 0, Shorts.BYTES);
95      scratch.clear();
96      return this;
97    }
98
99    @Override public Hasher putInt(int i) {
100      checkNotDone();
101      scratch.putInt(i);
102      digest.update(scratch.array(), 0, Ints.BYTES);
103      scratch.clear();
104      return this;
105    }
106
107    @Override public Hasher putLong(long l) {
108      checkNotDone();
109      scratch.putLong(l);
110      digest.update(scratch.array(), 0, Longs.BYTES);
111      scratch.clear();
112      return this;
113    }
114
115    @Override public Hasher putFloat(float f) {
116      checkNotDone();
117      scratch.putFloat(f);
118      digest.update(scratch.array(), 0, 4);
119      scratch.clear();
120      return this;
121    }
122
123    @Override public Hasher putDouble(double d) {
124      checkNotDone();
125      scratch.putDouble(d);
126      digest.update(scratch.array(), 0, 8);
127      scratch.clear();
128      return this;
129    }
130
131    @Override public Hasher putBoolean(boolean b) {
132      return putByte(b ? (byte) 1 : (byte) 0);
133    }
134
135    @Override public Hasher putChar(char c) {
136      checkNotDone();
137      scratch.putChar(c);
138      digest.update(scratch.array(), 0, Chars.BYTES);
139      scratch.clear();
140      return this;
141    }
142
143    @Override public Hasher putString(CharSequence charSequence) {
144      for (int i = 0; i < charSequence.length(); i++) {
145        putChar(charSequence.charAt(i));
146      }
147      return this;
148    }
149
150    @Override public Hasher putString(CharSequence charSequence, Charset charset) {
151      try {
152        return putBytes(charSequence.toString().getBytes(charset.name()));
153      } catch (java.io.UnsupportedEncodingException impossible) {
154        throw new AssertionError(impossible);
155      }
156    }
157
158    @Override public <T> Hasher putObject(T instance, Funnel<? super T> funnel) {
159      checkNotDone();
160      funnel.funnel(instance, this);
161      return this;
162    }
163
164    private void checkNotDone() {
165      checkState(!done, "Cannot use Hasher after calling #hash() on it");
166    }
167
168    public HashCode hash() {
169      done = true;
170      return HashCodes.fromBytes(digest.digest());
171    }
172  }
173}
174