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