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