• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.xtremelabs.robolectric.bytecode;
2 
3 import java.io.ByteArrayOutputStream;
4 import java.io.File;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.Enumeration;
9 import java.util.HashMap;
10 import java.util.Map;
11 import java.util.jar.Attributes;
12 import java.util.jar.JarEntry;
13 import java.util.jar.JarFile;
14 import java.util.jar.JarOutputStream;
15 import java.util.jar.Manifest;
16 
17 public class ClassCache {
18     private static final Attributes.Name VERSION_ATTRIBUTE = new Attributes.Name("version");
19 
20     private Map<String, byte[]> cachedClasses = new HashMap<String, byte[]>();
21     private boolean startedWriting = false;
22 
ClassCache(String classCachePath, final int expectedCacheVersion)23     public ClassCache(String classCachePath, final int expectedCacheVersion) {
24         final File cacheJarFile = new File(classCachePath);
25         JarFile cacheFile;
26         try {
27             cacheFile = new JarFile(cacheJarFile);
28             int cacheVersion = 0;
29             Manifest manifest = cacheFile.getManifest();
30             if (manifest != null) {
31                 Attributes attributes = manifest.getEntries().get("robolectric");
32                 if (attributes != null) {
33                     String cacheVersionStr = (String) attributes.get(VERSION_ATTRIBUTE);
34                     if (cacheVersionStr != null) {
35                         cacheVersion = Integer.parseInt(cacheVersionStr);
36                     }
37                 }
38             }
39             if (cacheVersion != expectedCacheVersion) {
40                 cacheJarFile.delete();
41             } else {
42                 readEntries(cacheFile);
43             }
44         } catch (IOException e) {
45             // no problem
46         }
47 
48         Runtime.getRuntime().addShutdownHook(new Thread() {
49             @Override public void run() {
50                 Manifest manifest = new Manifest();
51                 Attributes attributes = new Attributes();
52                 attributes.put(VERSION_ATTRIBUTE, String.valueOf(expectedCacheVersion));
53                 manifest.getEntries().put("robolectric", attributes);
54 
55                 saveAllClassesToCache(cacheJarFile, manifest);
56             }
57         });
58     }
59 
getClassBytesFor(String name)60     public byte[] getClassBytesFor(String name) {
61         return cachedClasses.get(name);
62     }
63 
isWriting()64     public boolean isWriting() {
65         synchronized (this) {
66             return startedWriting;
67         }
68     }
69 
addClass(String className, byte[] classBytes)70     public void addClass(String className, byte[] classBytes) {
71         cachedClasses.put(className, classBytes);
72     }
73 
readEntries(JarFile cacheFile)74     private void readEntries(JarFile cacheFile) {
75         Enumeration<JarEntry> entries = cacheFile.entries();
76         try {
77             while (entries.hasMoreElements()) {
78                 JarEntry entry = entries.nextElement();
79                 String className = entry.getName();
80                 if (className.endsWith(".class")) {
81                     int classSize = (int) entry.getSize();
82                     InputStream inputStream = cacheFile.getInputStream(entry);
83                     ByteArrayOutputStream baos = new ByteArrayOutputStream(classSize);
84                     int c;
85                     while ((c = inputStream.read()) != -1) {
86                         baos.write(c);
87                     }
88                     className = className.substring(0, className.indexOf(".class")).replace('/', '.');
89                     addClass(className, baos.toByteArray());
90                 }
91 
92             }
93         } catch (IOException e) {
94             // no problem, we didn't want those bytes that much anyway
95         }
96     }
97 
saveAllClassesToCache(File file, Manifest manifest)98     protected void saveAllClassesToCache(File file, Manifest manifest) {
99         synchronized (this) {
100             startedWriting = true;
101 
102             if (cachedClasses.size() > 0) {
103                 JarOutputStream jarOutputStream = null;
104                 try {
105                     File cacheJarDir = file.getParentFile();
106                     if (!cacheJarDir.exists()) {
107                         cacheJarDir.mkdirs();
108                     }
109 
110                     jarOutputStream = new JarOutputStream(new FileOutputStream(file), manifest);
111                     for (Map.Entry<String, byte[]> entry : cachedClasses.entrySet()) {
112                         String key = entry.getKey();
113                         jarOutputStream.putNextEntry(new JarEntry(key.replace('.', '/') + ".class"));
114                         jarOutputStream.write(entry.getValue());
115                         jarOutputStream.closeEntry();
116                     }
117                 } catch (IOException e) {
118                     throw new RuntimeException(e);
119                 } finally {
120                     if (jarOutputStream != null) {
121                         try {
122                             jarOutputStream.close();
123                         } catch (IOException ignore) {
124                         }
125                     }
126                 }
127             }
128             startedWriting = false;
129         }
130     }
131 }
132