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