package org.robolectric.internal.dependency; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.URL; import java.util.Date; import java.util.zip.CRC32; public class CachedDependencyResolver implements DependencyResolver { private final static String CACHE_PREFIX = "localArtifactUrl"; private final DependencyResolver dependencyResolver; private final CacheNamingStrategy cacheNamingStrategy; private final CacheValidationStrategy cacheValidationStrategy; private final Cache cache; public CachedDependencyResolver(DependencyResolver dependencyResolver, File cacheDir, long cacheValidTime) { this(dependencyResolver, new FileCache(cacheDir, cacheValidTime), new DefaultCacheNamingStrategy(), new DefaultCacheValidationStrategy()); } public CachedDependencyResolver(DependencyResolver dependencyResolver, Cache cache, CacheNamingStrategy cacheNamingStrategy, CacheValidationStrategy cacheValidationStrategy) { this.dependencyResolver = dependencyResolver; this.cache = cache; this.cacheNamingStrategy = cacheNamingStrategy; this.cacheValidationStrategy = cacheValidationStrategy; } @Override public URL getLocalArtifactUrl(DependencyJar dependency) { final String cacheName = cacheNamingStrategy.getName(CACHE_PREFIX, dependency); final URL urlFromCache = cache.load(cacheName, URL.class); if (urlFromCache != null && cacheValidationStrategy.isValid(urlFromCache)) { return urlFromCache; } final URL url = dependencyResolver.getLocalArtifactUrl(dependency); cache.write(cacheName, url); return url; } interface CacheNamingStrategy { String getName(String prefix, DependencyJar... dependencies); } interface CacheValidationStrategy { boolean isValid(URL url); boolean isValid(URL[] urls); } static class DefaultCacheValidationStrategy implements CacheValidationStrategy { @Override public boolean isValid(URL url) { return new File(url.getPath()).exists(); } @Override public boolean isValid(URL[] urls) { for (URL url : urls) { if (!isValid(url)) { return false; } } return true; } } static class DefaultCacheNamingStrategy implements CacheNamingStrategy { public String getName(String prefix, DependencyJar... dependencies) { StringBuilder sb = new StringBuilder(); sb.append(prefix) .append("#"); for (DependencyJar dependency : dependencies) { sb.append(dependency.getGroupId()) .append(":") .append(dependency.getArtifactId()) .append(":") .append(dependency.getVersion()) .append(","); } CRC32 crc = new CRC32(); crc.update(sb.toString().getBytes()); return crc.getValue() + ""; } } interface Cache { <T extends Serializable> T load(String id, Class<T> type); <T extends Serializable> boolean write(String id, T object); } static class FileCache implements Cache { private final File dir; private final long validTime; FileCache(File dir, long validTime) { this.dir = dir; this.validTime = validTime; } @Override public <T extends Serializable> T load(String id, Class<T> type) { try { File file = new File(dir, id); if (!file.exists() || validTime > 0 && file.lastModified() < new Date().getTime() - validTime) { return null; } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { Object o = in.readObject(); return o.getClass() == type ? (T) o : null; } } catch (IOException | ClassNotFoundException e) { return null; } } @Override public <T extends Serializable> boolean write(String id, T object) { try { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File(dir, id)))) { out.writeObject(object); return true; } } catch (IOException e) { return false; } } } }