package org.robolectric;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.robolectric.annotation.Config;
import org.robolectric.util.Join;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import static com.google.common.collect.Lists.reverse;
public class ConfigMerger {
private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 10;
}
};
/**
* Calculate the {@link Config} for the given test.
*
* @param testClass the class containing the test
* @param method the test method
* @param globalConfig global configuration values
* @return the effective configuration
* @since 3.2
*/
public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
Config config = Config.Builder.defaults().build();
config = override(config, globalConfig);
for (String packageName : reverse(packageHierarchyOf(testClass))) {
Config packageConfig = cachedPackageConfig(packageName);
config = override(config, packageConfig);
}
for (Class clazz : reverse(parentClassesFor(testClass))) {
Config classConfig = (Config) clazz.getAnnotation(Config.class);
config = override(config, classConfig);
}
Config methodConfig = method.getAnnotation(Config.class);
config = override(config, methodConfig);
return config;
}
/**
* Generate {@link Config} for the specified package.
*
* More specific packages, test classes, and test method configurations
* will override values provided here.
*
* The default implementation uses properties provided by {@link #getConfigProperties(String)}.
*
* The returned object is likely to be reused for many tests.
*
* @param packageName the name of the package, or empty string ({@code ""}) for the top level package
* @return {@link Config} object for the specified package
* @since 3.2
*/
@Nullable
private Config buildPackageConfig(String packageName) {
return Config.Implementation.fromProperties(getConfigProperties(packageName));
}
/**
* Return a {@link Properties} file for the given package name, or {@code null} if none is available.
*
* @since 3.2
*/
protected Properties getConfigProperties(String packageName) {
List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
final String resourceName = Join.join("/", packageParts);
try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
if (resourceAsStream == null) return null;
Properties properties = new Properties();
properties.load(resourceAsStream);
return properties;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Nonnull @VisibleForTesting
List<String> packageHierarchyOf(Class<?> javaClass) {
Package aPackage = javaClass.getPackage();
String testPackageName = aPackage == null ? "" : aPackage.getName();
List<String> packageHierarchy = new ArrayList<>();
while (!testPackageName.isEmpty()) {
packageHierarchy.add(testPackageName);
int lastDot = testPackageName.lastIndexOf('.');
testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
}
packageHierarchy.add("");
return packageHierarchy;
}
@Nonnull
private List<Class> parentClassesFor(Class testClass) {
List<Class> testClassHierarchy = new ArrayList<>();
while (testClass != null && !testClass.equals(Object.class)) {
testClassHierarchy.add(testClass);
testClass = testClass.getSuperclass();
}
return testClassHierarchy;
}
private Config override(Config config, Config classConfig) {
return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
}
@Nullable
private Config cachedPackageConfig(String packageName) {
synchronized (packageConfigCache) {
Config config = packageConfigCache.get(packageName);
if (config == null && !packageConfigCache.containsKey(packageName)) {
config = buildPackageConfig(packageName);
packageConfigCache.put(packageName, config);
}
return config;
}
}
// visible for testing
@SuppressWarnings("WeakerAccess")
InputStream getResourceAsStream(String resourceName) {
return getClass().getClassLoader().getResourceAsStream(resourceName);
}
}