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 java.io.File;
20 import java.io.FileInputStream;
21 import java.security.MessageDigest;
22 
23 /**
24  * Caches content by MD5.
25  */
26 public 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      */
Md5Cache(Log log, String keyPrefix, FileCache fileCache)36     public Md5Cache(Log log, String keyPrefix, FileCache fileCache) {
37         this.log = log;
38         this.keyPrefix = keyPrefix;
39         this.fileCache = fileCache;
40     }
41 
getFromCache(File output, String key)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      */
md5(File file)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 
byteArrayToHexString(byte[] bytes)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      */
makeKey(Classpath classpath)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      */
makeKey(File file)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      */
insert(String key, File content)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