1 package org.robolectric.internal.dependency; 2 3 import static java.nio.charset.StandardCharsets.UTF_8; 4 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileOutputStream; 8 import java.io.IOException; 9 import java.io.ObjectInputStream; 10 import java.io.ObjectOutputStream; 11 import java.io.Serializable; 12 import java.net.URL; 13 import java.util.Date; 14 import java.util.zip.CRC32; 15 16 public class CachedDependencyResolver implements DependencyResolver { 17 private final static String CACHE_PREFIX = "localArtifactUrl"; 18 19 private final DependencyResolver dependencyResolver; 20 private final CacheNamingStrategy cacheNamingStrategy; 21 private final CacheValidationStrategy cacheValidationStrategy; 22 private final Cache cache; 23 24 public CachedDependencyResolver(DependencyResolver dependencyResolver, File cacheDir, long cacheValidTime) { 25 this(dependencyResolver, new FileCache(cacheDir, cacheValidTime), new DefaultCacheNamingStrategy(), new DefaultCacheValidationStrategy()); 26 } 27 28 public CachedDependencyResolver(DependencyResolver dependencyResolver, Cache cache, CacheNamingStrategy cacheNamingStrategy, CacheValidationStrategy cacheValidationStrategy) { 29 this.dependencyResolver = dependencyResolver; 30 this.cache = cache; 31 this.cacheNamingStrategy = cacheNamingStrategy; 32 this.cacheValidationStrategy = cacheValidationStrategy; 33 } 34 35 @Override 36 public URL getLocalArtifactUrl(DependencyJar dependency) { 37 final String cacheName = cacheNamingStrategy.getName(CACHE_PREFIX, dependency); 38 final URL urlFromCache = cache.load(cacheName, URL.class); 39 40 if (urlFromCache != null && cacheValidationStrategy.isValid(urlFromCache)) { 41 return urlFromCache; 42 } 43 44 final URL url = dependencyResolver.getLocalArtifactUrl(dependency); 45 cache.write(cacheName, url); 46 return url; 47 } 48 49 interface CacheNamingStrategy { 50 String getName(String prefix, DependencyJar... dependencies); 51 } 52 53 interface CacheValidationStrategy { 54 boolean isValid(URL url); 55 56 boolean isValid(URL[] urls); 57 } 58 59 static class DefaultCacheValidationStrategy implements CacheValidationStrategy { 60 @Override 61 public boolean isValid(URL url) { 62 return new File(url.getPath()).exists(); 63 } 64 65 @Override 66 public boolean isValid(URL[] urls) { 67 for (URL url : urls) { 68 if (!isValid(url)) { 69 return false; 70 } 71 } 72 return true; 73 } 74 } 75 76 static class DefaultCacheNamingStrategy implements CacheNamingStrategy { 77 @Override public String getName(String prefix, DependencyJar... dependencies) { 78 StringBuilder sb = new StringBuilder(); 79 80 sb.append(prefix) 81 .append("#"); 82 83 for (DependencyJar dependency : dependencies) { 84 sb.append(dependency.getGroupId()) 85 .append(":") 86 .append(dependency.getArtifactId()) 87 .append(":") 88 .append(dependency.getVersion()) 89 .append(","); 90 } 91 92 CRC32 crc = new CRC32(); 93 crc.update(sb.toString().getBytes(UTF_8)); 94 return crc.getValue() + ""; 95 } 96 } 97 98 interface Cache { 99 <T extends Serializable> T load(String id, Class<T> type); 100 <T extends Serializable> boolean write(String id, T object); 101 } 102 103 static class FileCache implements Cache { 104 private final File dir; 105 private final long validTime; 106 107 FileCache(File dir, long validTime) { 108 this.dir = dir; 109 this.validTime = validTime; 110 } 111 112 @Override 113 public <T extends Serializable> T load(String id, Class<T> type) { 114 try { 115 File file = new File(dir, id); 116 if (!file.exists() || (validTime > 0 && file.lastModified() < new Date().getTime() - validTime)) { 117 return null; 118 } 119 120 try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { 121 Object o = in.readObject(); 122 return o.getClass() == type ? (T) o : null; 123 } 124 } catch (IOException | ClassNotFoundException e) { 125 return null; 126 } 127 } 128 129 @Override 130 public <T extends Serializable> boolean write(String id, T object) { 131 try { 132 try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File(dir, id)))) { 133 out.writeObject(object); 134 return true; 135 } 136 } catch (IOException e) { 137 return false; 138 } 139 } 140 } 141 } 142