1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package vogar; 18 19 import com.google.common.base.Charsets; 20 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.security.MessageDigest; 24 25 /** 26 * Caches content by MD5. 27 */ 28 public final class Md5Cache { 29 30 private final Log log; 31 private final String keyPrefix; 32 private final FileCache fileCache; 33 34 /** 35 * Creates a new cache accessor. There's only one directory on disk, so 'keyPrefix' is really 36 * just a convenience for humans inspecting the cache. 37 */ 38 public Md5Cache(Log log, String keyPrefix, FileCache fileCache) { 39 this.log = log; 40 this.keyPrefix = keyPrefix; 41 this.fileCache = fileCache; 42 } 43 44 public boolean getFromCache(File output, String key) { 45 if (fileCache.existsInCache(key)) { 46 fileCache.copyFromCache(key, output); 47 return true; 48 } 49 return false; 50 } 51 52 /** 53 * Returns an ASCII hex representation of the MD5 of the content of 'file'. 54 */ 55 private static String md5(File file) { 56 byte[] digest = null; 57 try { 58 MessageDigest digester = MessageDigest.getInstance("MD5"); 59 byte[] bytes = new byte[8192]; 60 FileInputStream in = new FileInputStream(file); 61 try { 62 int byteCount; 63 while ((byteCount = in.read(bytes)) > 0) { 64 digester.update(bytes, 0, byteCount); 65 } 66 digest = digester.digest(); 67 } finally { 68 in.close(); 69 } 70 } catch (Exception cause) { 71 throw new RuntimeException("Unable to compute MD5 of \"" + file + "\"", cause); 72 } 73 return (digest == null) ? null : byteArrayToHexString(digest); 74 } 75 76 /** 77 * Returns an ASCII hex representation of the MD5 of 'string'. 78 */ 79 private static String md5(String string) { 80 byte[] digest; 81 try { 82 MessageDigest digester = MessageDigest.getInstance("MD5"); 83 digester.update(string.getBytes(Charsets.UTF_8)); 84 digest = digester.digest(); 85 } catch (Exception cause) { 86 throw new RuntimeException("Unable to compute MD5 of \"" + string + "\"", cause); 87 } 88 return (digest == null) ? null : byteArrayToHexString(digest); 89 } 90 91 private static String byteArrayToHexString(byte[] bytes) { 92 StringBuilder result = new StringBuilder(); 93 for (byte b : bytes) { 94 result.append(Integer.toHexString((b >> 4) & 0xf)); 95 result.append(Integer.toHexString(b & 0xf)); 96 } 97 return result.toString(); 98 } 99 100 /** 101 * Returns the appropriate key for a dex file corresponding to the contents of 'classpath'. 102 * Returns null if we don't think it's possible to cache the given classpath. 103 */ 104 public String makeKey(Classpath classpath) { 105 // Do we have it in cache? 106 String key = keyPrefix; 107 for (File element : classpath.getElements()) { 108 // We only cache dexed .jar/.jack files, not directories. 109 String fileName = element.getName(); 110 if (!fileName.endsWith(".jar") && !fileName.endsWith(".jack")) { 111 return null; 112 } 113 key += "-" + md5(element); 114 } 115 return key; 116 } 117 118 /** 119 * Returns a key corresponding to the MD5ed contents of {@code file}. 120 */ 121 public String makeKey(File file) { 122 return keyPrefix + "-" + md5(file); 123 } 124 125 /** 126 * Returns a key corresponding to the MD5ed contents of the element. 127 */ 128 public String makeKey(String... elements) { 129 StringBuilder sb = new StringBuilder(); 130 for (String element : elements) { 131 sb.append(element); 132 sb.append('|'); 133 } 134 return keyPrefix + "-" + md5(sb.toString()); 135 } 136 137 /** 138 * Copy the file 'content' into the cache with the given 'key'. 139 * This method assumes you're using the appropriate key for the content (and has no way to 140 * check because the key is a function of the inputs that made the content, not the content 141 * itself). 142 * We accept a null so the caller doesn't have to pay attention to whether we think we can 143 * cache the content or not. 144 */ 145 public void insert(String key, File content) { 146 if (key == null) { 147 return; 148 } 149 log.verbose("inserting " + key); 150 fileCache.copyToCache(content, key); 151 } 152 } 153