17850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/* 27850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project 37850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 47850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License"); 57850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * you may not use this file except in compliance with the License. 67850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * You may obtain a copy of the License at 77850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 87850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * http://www.apache.org/licenses/LICENSE-2.0 97850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Unless required by applicable law or agreed to in writing, software 117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS, 127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See the License for the specific language governing permissions and 147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * limitations under the License. 157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compackage vogar; 187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 19963abb73aba37a071cd914516e056ae89d343426Neil Fullerimport com.google.common.base.Charsets; 20963abb73aba37a071cd914516e056ae89d343426Neil Fuller 217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.io.File; 227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.io.FileInputStream; 237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.security.MessageDigest; 247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/** 267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Caches content by MD5. 277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compublic final class Md5Cache { 297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 30b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com private final Log log; 317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com private final String keyPrefix; 322c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com private final FileCache fileCache; 337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com /** 357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Creates a new cache accessor. There's only one directory on disk, so 'keyPrefix' is really 367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * just a convenience for humans inspecting the cache. 377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 38b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com public Md5Cache(Log log, String keyPrefix, FileCache fileCache) { 39b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com this.log = log; 407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com this.keyPrefix = keyPrefix; 412c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com this.fileCache = fileCache; 4241a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com } 4341a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com 442c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com public boolean getFromCache(File output, String key) { 452c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com if (fileCache.existsInCache(key)) { 462c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com fileCache.copyFromCache(key, output); 4741a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com return true; 4841a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com } 4941a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com return false; 507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com /** 537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Returns an ASCII hex representation of the MD5 of the content of 'file'. 547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com private static String md5(File file) { 567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com byte[] digest = null; 577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com MessageDigest digester = MessageDigest.getInstance("MD5"); 597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com byte[] bytes = new byte[8192]; 607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com FileInputStream in = new FileInputStream(file); 617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com int byteCount; 637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com while ((byteCount = in.read(bytes)) > 0) { 647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com digester.update(bytes, 0, byteCount); 657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com digest = digester.digest(); 677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } finally { 687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com in.close(); 697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (Exception cause) { 717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com throw new RuntimeException("Unable to compute MD5 of \"" + file + "\"", cause); 727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return (digest == null) ? null : byteArrayToHexString(digest); 747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 76963abb73aba37a071cd914516e056ae89d343426Neil Fuller /** 77963abb73aba37a071cd914516e056ae89d343426Neil Fuller * Returns an ASCII hex representation of the MD5 of 'string'. 78963abb73aba37a071cd914516e056ae89d343426Neil Fuller */ 79963abb73aba37a071cd914516e056ae89d343426Neil Fuller private static String md5(String string) { 80963abb73aba37a071cd914516e056ae89d343426Neil Fuller byte[] digest; 81963abb73aba37a071cd914516e056ae89d343426Neil Fuller try { 82963abb73aba37a071cd914516e056ae89d343426Neil Fuller MessageDigest digester = MessageDigest.getInstance("MD5"); 83963abb73aba37a071cd914516e056ae89d343426Neil Fuller digester.update(string.getBytes(Charsets.UTF_8)); 84963abb73aba37a071cd914516e056ae89d343426Neil Fuller digest = digester.digest(); 85963abb73aba37a071cd914516e056ae89d343426Neil Fuller } catch (Exception cause) { 86963abb73aba37a071cd914516e056ae89d343426Neil Fuller throw new RuntimeException("Unable to compute MD5 of \"" + string + "\"", cause); 87963abb73aba37a071cd914516e056ae89d343426Neil Fuller } 88963abb73aba37a071cd914516e056ae89d343426Neil Fuller return (digest == null) ? null : byteArrayToHexString(digest); 89963abb73aba37a071cd914516e056ae89d343426Neil Fuller } 90963abb73aba37a071cd914516e056ae89d343426Neil Fuller 917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com private static String byteArrayToHexString(byte[] bytes) { 927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com StringBuilder result = new StringBuilder(); 937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com for (byte b : bytes) { 947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com result.append(Integer.toHexString((b >> 4) & 0xf)); 957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com result.append(Integer.toHexString(b & 0xf)); 967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return result.toString(); 987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 1007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com /** 1017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Returns the appropriate key for a dex file corresponding to the contents of 'classpath'. 1027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Returns null if we don't think it's possible to cache the given classpath. 1037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 10441a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com public String makeKey(Classpath classpath) { 1057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com // Do we have it in cache? 1067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com String key = keyPrefix; 1077850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com for (File element : classpath.getElements()) { 108fe39f9e9b71d8a4b62e9b4873fbe779a004aa536Neil Fuller // We only cache dexed .jar files, not directories. 109963abb73aba37a071cd914516e056ae89d343426Neil Fuller String fileName = element.getName(); 110fe39f9e9b71d8a4b62e9b4873fbe779a004aa536Neil Fuller if (!fileName.endsWith(".jar")) { 1117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 1127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 1137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com key += "-" + md5(element); 1147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 11541a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com return key; 11641a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com } 11741a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com 11841a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com /** 11941a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com * Returns a key corresponding to the MD5ed contents of {@code file}. 12041a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com */ 12141a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com public String makeKey(File file) { 12241a680807eb80e63fbfe42c218c9e1da6615d867jsharpe@google.com return keyPrefix + "-" + md5(file); 1237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 1247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 1257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com /** 126963abb73aba37a071cd914516e056ae89d343426Neil Fuller * Returns a key corresponding to the MD5ed contents of the element. 127963abb73aba37a071cd914516e056ae89d343426Neil Fuller */ 128dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root public String makeKey(String... elements) { 129dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root StringBuilder sb = new StringBuilder(); 130dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root for (String element : elements) { 131dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root sb.append(element); 132dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root sb.append('|'); 133dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root } 134dbe39a9c477cb5df1ebc6dd14ceadd3b9d32f48bKenny Root return keyPrefix + "-" + md5(sb.toString()); 135963abb73aba37a071cd914516e056ae89d343426Neil Fuller } 136963abb73aba37a071cd914516e056ae89d343426Neil Fuller 137963abb73aba37a071cd914516e056ae89d343426Neil Fuller /** 1387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copy the file 'content' into the cache with the given 'key'. 1397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * This method assumes you're using the appropriate key for the content (and has no way to 1407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * check because the key is a function of the inputs that made the content, not the content 1417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * itself). 1427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * We accept a null so the caller doesn't have to pay attention to whether we think we can 1437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * cache the content or not. 1447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 1452c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com public void insert(String key, File content) { 1467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com if (key == null) { 1477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return; 1487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 149b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com log.verbose("inserting " + key); 1502c67cf17be7262353493480d98fb88ad0f8fc320jsharpe@google.com fileCache.copyToCache(content, key); 1517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 1527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com} 153