ConfigMerger.java revision b1bfc1aa31f349fc12da4d7f3052880e3e97536e
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 Package aPackage = javaClass.getPackage(); 95 String testPackageName = aPackage == null ? "" : aPackage.getName(); 96 List<String> packageHierarchy = new ArrayList<>(); 97 while (!testPackageName.isEmpty()) { 98 packageHierarchy.add(testPackageName); 99 int lastDot = testPackageName.lastIndexOf('.'); 100 testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : ""; 101 } 102 packageHierarchy.add(""); 103 return packageHierarchy; 104 } 105 106 @NotNull 107 private List<Class> parentClassesFor(Class testClass) { 108 List<Class> testClassHierarchy = new ArrayList<>(); 109 while (testClass != null && !testClass.equals(Object.class)) { 110 testClassHierarchy.add(testClass); 111 testClass = testClass.getSuperclass(); 112 } 113 return testClassHierarchy; 114 } 115 116 private Config override(Config config, Config classConfig) { 117 return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config; 118 } 119 120 @Nullable 121 private Config cachedPackageConfig(String packageName) { 122 synchronized (packageConfigCache) { 123 Config config = packageConfigCache.get(packageName); 124 if (config == null && !packageConfigCache.containsKey(packageName)) { 125 config = buildPackageConfig(packageName); 126 packageConfigCache.put(packageName, config); 127 } 128 return config; 129 } 130 } 131 132 // visible for testing 133 @SuppressWarnings("WeakerAccess") 134 InputStream getResourceAsStream(String resourceName) { 135 return getClass().getClassLoader().getResourceAsStream(resourceName); 136 } 137} 138