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
17package vogar;
18
19import java.io.File;
20import java.io.FileInputStream;
21import java.security.MessageDigest;
22
23/**
24 * Caches content by MD5.
25 */
26public final class Md5Cache {
27
28    private final Log log;
29    private final String keyPrefix;
30    private final FileCache fileCache;
31
32    /**
33     * Creates a new cache accessor. There's only one directory on disk, so 'keyPrefix' is really
34     * just a convenience for humans inspecting the cache.
35     */
36    public Md5Cache(Log log, String keyPrefix, FileCache fileCache) {
37        this.log = log;
38        this.keyPrefix = keyPrefix;
39        this.fileCache = fileCache;
40    }
41
42    public boolean getFromCache(File output, String key) {
43        if (fileCache.existsInCache(key)) {
44            fileCache.copyFromCache(key, output);
45            return true;
46        }
47        return false;
48    }
49
50    /**
51     * Returns an ASCII hex representation of the MD5 of the content of 'file'.
52     */
53    private static String md5(File file) {
54        byte[] digest = null;
55        try {
56            MessageDigest digester = MessageDigest.getInstance("MD5");
57            byte[] bytes = new byte[8192];
58            FileInputStream in = new FileInputStream(file);
59            try {
60                int byteCount;
61                while ((byteCount = in.read(bytes)) > 0) {
62                    digester.update(bytes, 0, byteCount);
63                }
64                digest = digester.digest();
65            } finally {
66                in.close();
67            }
68        } catch (Exception cause) {
69            throw new RuntimeException("Unable to compute MD5 of \"" + file + "\"", cause);
70        }
71        return (digest == null) ? null : byteArrayToHexString(digest);
72    }
73
74    private static String byteArrayToHexString(byte[] bytes) {
75        StringBuilder result = new StringBuilder();
76        for (byte b : bytes) {
77            result.append(Integer.toHexString((b >> 4) & 0xf));
78            result.append(Integer.toHexString(b & 0xf));
79        }
80        return result.toString();
81    }
82
83    /**
84     * Returns the appropriate key for a dex file corresponding to the contents of 'classpath'.
85     * Returns null if we don't think it's possible to cache the given classpath.
86     */
87    public String makeKey(Classpath classpath) {
88        // Do we have it in cache?
89        String key = keyPrefix;
90        for (File element : classpath.getElements()) {
91            // We only cache dexed .jar files, not directories.
92            if (!element.toString().endsWith(".jar")) {
93                return null;
94            }
95            key += "-" + md5(element);
96        }
97        return key;
98    }
99
100    /**
101     * Returns a key corresponding to the MD5ed contents of {@code file}.
102     */
103    public String makeKey(File file) {
104        return keyPrefix + "-" + md5(file);
105    }
106
107    /**
108     * Copy the file 'content' into the cache with the given 'key'.
109     * This method assumes you're using the appropriate key for the content (and has no way to
110     * check because the key is a function of the inputs that made the content, not the content
111     * itself).
112     * We accept a null so the caller doesn't have to pay attention to whether we think we can
113     * cache the content or not.
114     */
115    public void insert(String key, File content) {
116        if (key == null) {
117            return;
118        }
119        log.verbose("inserting " + key);
120        fileCache.copyToCache(content, key);
121    }
122}
123