diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/util/Fingerprint.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/util/Fingerprint.java | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java new file mode 100644 index 0000000000..e4c0876737 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java @@ -0,0 +1,319 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.util; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.UUID; + +/** + * Simplified wrapper for MD5 message digests. See also + * com.google.math.crypto.MD5HMAC for a similar interface. + * + * @see java.security.MessageDigest + */ +public final class Fingerprint { + + private final MessageDigest md; + + /** + * Creates and initializes a new MD5 object; if this fails, Java must be + * installed incorrectly. + */ + public Fingerprint() { + try { + md = MessageDigest.getInstance("md5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 not available"); + } + } + + /** + * Completes the hash computation by doing final operations, e.g., padding. + * + * <p>This method has the side-effect of resetting the underlying digest computer. + * + * @return the MD5 digest as a 16-byte array + * @see java.security.MessageDigest#digest() + */ + public byte[] digestAndReset() { + return md.digest(); + } + + /** + * Completes the hash computation and returns the digest as a string. + * + * <p>This method has the side-effect of resetting the underlying digest computer. + * + * @return the MD5 digest as a 32-character string of hexadecimal digits + * @see com.google.math.crypto.MD5HMAC#toString() + */ + public String hexDigestAndReset() { + return hexDigest(digestAndReset()); + } + + /** + * Returns a string representation of an MD5 digest. + * + * @param digest the MD5 digest, perhaps from a previous call to digest + * @return the digest as a 32-character string of hexadecimal digits + */ + public static String hexDigest(byte[] digest) { + StringBuilder b = new StringBuilder(32); + for (int i = 0; i < digest.length; i++) { + int n = digest[i]; + b.append("0123456789abcdef".charAt((n >> 4) & 0xF)); + b.append("0123456789abcdef".charAt(n & 0xF)); + } + return b.toString(); + } + + /** + * Override of Object.toString to return a string for the MD5 digest without + * finalizing the digest computation. Calling hexDigest() instead will + * finalize the digest computation. + * + * @return the string returned by hexDigest() + */ + @Override + public String toString() { + try { + // MD5 does support cloning, so this should not fail + return hexDigest(((MessageDigest) md.clone()).digest()); + } catch (CloneNotSupportedException e) { + // MessageDigest does not support cloning, + // so just return the toString() on the MessageDigest. + return md.toString(); + } + } + + /** + * Updates the digest with 0 or more bytes. + * + * @param input the array of bytes with which to update the digest + * @see java.security.MessageDigest#update(byte[]) + */ + public Fingerprint addBytes(byte[] input) { + md.update(input); + return this; + } + + /** + * Updates the digest with the specified number of bytes starting at offset. + * + * @param input the array of bytes with which to update the digest + * @param offset the offset into the array + * @param len the number of bytes to use + * @see java.security.MessageDigest#update(byte[], int, int) + */ + public Fingerprint addBytes(byte[] input, int offset, int len) { + md.update(input, offset, len); + return this; + } + + /** + * Updates the digest with a boolean value. + */ + public Fingerprint addBoolean(boolean input) { + addBytes(new byte[] { (byte) (input ? 1 : 0) }); + return this; + } + + /** + * Updates the digest with the little-endian bytes of a given int value. + * + * @param input the integer with which to update the digest + */ + public Fingerprint addInt(int input) { + md.update(new byte[] { + (byte) input, + (byte) (input >> 8), + (byte) (input >> 16), + (byte) (input >> 24), + }); + + return this; + } + + /** + * Updates the digest with the little-endian bytes of a given long value. + * + * @param input the long with which to update the digest + */ + public Fingerprint addLong(long input) { + md.update(new byte[]{ + (byte) input, + (byte) (input >> 8), + (byte) (input >> 16), + (byte) (input >> 24), + (byte) (input >> 32), + (byte) (input >> 40), + (byte) (input >> 48), + (byte) (input >> 56), + }); + + return this; + } + + /** + * Updates the digest with a UUID. + * + * @param uuid the UUID with which to update the digest. Must not be null. + */ + public Fingerprint addUUID(UUID uuid) { + addLong(uuid.getLeastSignificantBits()); + addLong(uuid.getMostSignificantBits()); + return this; + } + + /** + * Updates the digest with a String using its length plus its UTF8 encoded bytes. + * + * @param input the String with which to update the digest + * @see java.security.MessageDigest#update(byte[]) + */ + public Fingerprint addString(String input) { + byte[] bytes = input.getBytes(UTF_8); + addInt(bytes.length); + md.update(bytes); + return this; + } + + /** + * Updates the digest with a String using its length and content. + * + * @param input the String with which to update the digest + * @see java.security.MessageDigest#update(byte[]) + */ + public Fingerprint addStringLatin1(String input) { + addInt(input.length()); + byte[] bytes = new byte[input.length()]; + for (int i = 0; i < input.length(); i++) { + bytes[i] = (byte) input.charAt(i); + } + md.update(bytes); + return this; + } + + /** + * Updates the digest with a Path. + * + * @param input the Path with which to update the digest. + */ + public Fingerprint addPath(Path input) { + addStringLatin1(input.getPathString()); + return this; + } + + /** + * Updates the digest with a Path. + * + * @param input the Path with which to update the digest. + */ + public Fingerprint addPath(PathFragment input) { + addStringLatin1(input.getPathString()); + return this; + } + + /** + * Updates the digest with inputs by iterating over them and invoking + * {@code #addString(String)} on each element. + * + * @param inputs the inputs with which to update the digest + */ + public Fingerprint addStrings(Iterable<String> inputs) { + addInt(Iterables.size(inputs)); + for (String input : inputs) { + addString(input); + } + + return this; + } + + /** + * Updates the digest with inputs by iterating over them and invoking + * {@code #addString(String)} on each element. + * + * @param inputs the inputs with which to update the digest + */ + public Fingerprint addStrings(String... inputs) { + addInt(inputs.length); + for (String input : inputs) { + addString(input); + } + + return this; + } + + /** + * Updates the digest with inputs which are pairs in a map, by iterating over + * the map entries and invoking {@code #addString(String)} on each key and + * value. + * + * @param inputs the inputs in a map with which to update the digest + */ + public Fingerprint addStringMap(Map<String, String> inputs) { + addInt(inputs.size()); + for (Map.Entry<String, String> entry : inputs.entrySet()) { + addString(entry.getKey()); + addString(entry.getValue()); + } + + return this; + } + + /** + * Updates the digest with a list of paths by iterating over them and + * invoking {@link #addPath(PathFragment)} on each element. + * + * @param inputs the paths with which to update the digest + */ + public Fingerprint addPaths(Iterable<PathFragment> inputs) { + addInt(Iterables.size(inputs)); + for (PathFragment path : inputs) { + addPath(path); + } + + return this; + } + + /** + * Reset the Fingerprint for additional use as though previous digesting had not been done. + */ + public void reset() { + md.reset(); + } + + // -------- Convenience methods ---------------------------- + + /** + * Computes the hex digest from a String using UTF8 encoding and returning + * the hexDigest(). + * + * @param input the String from which to compute the digest + */ + public static String md5Digest(String input) { + Fingerprint f = new Fingerprint(); + f.addBytes(input.getBytes(UTF_8)); + return f.hexDigestAndReset(); + } +} |