ConfigMerger.java revision 104b292a58958d310fcb27472fd7e450b3d99946
1package org.robolectric; 2 3import org.jetbrains.annotations.NotNull; 4import org.jetbrains.annotations.Nullable; 5import org.robolectric.annotation.Config; 6 7import java.io.IOException; 8import java.io.InputStream; 9import java.lang.reflect.Method; 10import java.util.ArrayList; 11import java.util.LinkedHashMap; 12import java.util.List; 13import java.util.Map; 14import java.util.Properties; 15 16import static com.google.common.collect.Lists.reverse; 17 18public class ConfigMerger { 19 private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() { 20 @Override 21 protected boolean removeEldestEntry(Map.Entry eldest) { 22 return size() > 10; 23 } 24 }; 25 26 public Config getConfig(Class<?> testClass, Method method, Config globalConfig) { 27 Config config = globalConfig; 28 29 for (String packageName : reverse(packagesFor(testClass))) { 30 Config packageConfig = cachedPackageConfig(packageName); 31 config = override(config, packageConfig); 32 } 33 34 for (Class clazz : reverse(parentClassesFor(testClass))) { 35 Config classConfig = (Config) clazz.getAnnotation(Config.class); 36 config = override(config, classConfig); 37 } 38 39 Config methodConfig = method.getAnnotation(Config.class); 40 config = override(config, methodConfig); 41 42 return config; 43 } 44 45 /** 46 * Generate {@link Config} for the specified package. 47 * 48 * More specific packages, test classes, and test method configurations 49 * will override values provided here. 50 * 51 * The default implementation uses properties provided by {@link #getConfigProperties(String)}. 52 * 53 * The returned object is likely to be reused for many tests. 54 * 55 * @param packageName the name of the package, or empty string (<code>""</code>) for the top level package 56 * @return {@link Config} object for the specified package 57 */ 58 @Nullable 59 private Config buildPackageConfig(String packageName) { 60 return Config.Implementation.fromProperties(getConfigProperties(packageName)); 61 } 62 63 /** 64 * Return a {@link Properties} file for the given package name, or <code>null</code> if none is available. 65 */ 66 protected Properties getConfigProperties(String packageName) { 67 String resourceName = packageName.replace('.', '/') + "/" + RobolectricTestRunner.CONFIG_PROPERTIES; 68 try (InputStream resourceAsStream = getResourceAsStream(resourceName)) { 69 if (resourceAsStream == null) return null; 70 Properties properties = new Properties(); 71 properties.load(resourceAsStream); 72 return properties; 73 } catch (IOException e) { 74 throw new RuntimeException(e); 75 } 76 } 77 78 @NotNull 79 private List<String> packagesFor(Class<?> javaClass) { 80 String testPackageName = javaClass.getPackage().getName(); 81 List<String> packageHierarchy = new ArrayList<>(); 82 while (!testPackageName.isEmpty()) { 83 packageHierarchy.add(testPackageName); 84 int lastDot = testPackageName.lastIndexOf('.'); 85 testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : ""; 86 } 87 packageHierarchy.add(""); 88 return packageHierarchy; 89 } 90 91 @NotNull 92 private List<Class> parentClassesFor(Class testClass) { 93 List<Class> testClassHierarchy = new ArrayList<>(); 94 while (testClass != null && !testClass.equals(Object.class)) { 95 testClassHierarchy.add(testClass); 96 testClass = testClass.getSuperclass(); 97 } 98 return testClassHierarchy; 99 } 100 101 private Config override(Config config, Config classConfig) { 102 return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config; 103 } 104 105 @Nullable 106 private Config cachedPackageConfig(String packageName) { 107 synchronized (packageConfigCache) { 108 Config config = packageConfigCache.get(packageName); 109 if (config == null && !packageConfigCache.containsKey(packageName)) { 110 config = buildPackageConfig(packageName); 111 packageConfigCache.put(packageName, config); 112 } 113 return config; 114 } 115 } 116 117 // visible for testing 118 @SuppressWarnings("WeakerAccess") 119 InputStream getResourceAsStream(String resourceName) { 120 return getClass().getClassLoader().getResourceAsStream(resourceName); 121 } 122} 123