1104b292a58958d310fcb27472fd7e450b3d99946Christian Williamspackage org.robolectric;
2104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
3851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport static com.google.common.collect.Lists.reverse;
4104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
5851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport com.google.common.annotations.VisibleForTesting;
6104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.io.IOException;
7104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.io.InputStream;
8104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.lang.reflect.Method;
9104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.ArrayList;
10165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Dananimport java.util.Arrays;
11104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.LinkedHashMap;
12104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.List;
13104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.Map;
14104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.Properties;
15851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport javax.annotation.Nonnull;
16851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport javax.annotation.Nullable;
17851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport org.robolectric.annotation.Config;
18851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport org.robolectric.util.Join;
19104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
20104b292a58958d310fcb27472fd7e450b3d99946Christian Williamspublic class ConfigMerger {
21104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() {
22104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    @Override
23104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    protected boolean removeEldestEntry(Map.Entry eldest) {
24104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      return size() > 10;
25104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
26104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  };
27104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
28a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams  /**
29a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * Calculate the {@link Config} for the given test.
30a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   *
31a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @param testClass the class containing the test
32a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @param method the test method
33a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @param globalConfig global configuration values
34a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @return the effective configuration
35a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @since 3.2
36a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   */
37104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
38a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams    Config config = Config.Builder.defaults().build();
39a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams    config = override(config, globalConfig);
40104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
418749eceadefcef06481f51a6915b75ec15aba6b2Christian Williams    for (String packageName : reverse(packageHierarchyOf(testClass))) {
42104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      Config packageConfig = cachedPackageConfig(packageName);
43104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      config = override(config, packageConfig);
44104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
45104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
46104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    for (Class clazz : reverse(parentClassesFor(testClass))) {
47104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      Config classConfig = (Config) clazz.getAnnotation(Config.class);
48104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      config = override(config, classConfig);
49104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
50104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
51104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    Config methodConfig = method.getAnnotation(Config.class);
52104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    config = override(config, methodConfig);
53104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
54104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return config;
55104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
56104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
57104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  /**
58104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * Generate {@link Config} for the specified package.
59104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   *
60104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * More specific packages, test classes, and test method configurations
61104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * will override values provided here.
62104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   *
63104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * The default implementation uses properties provided by {@link #getConfigProperties(String)}.
64104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   *
65104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * The returned object is likely to be reused for many tests.
66104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   *
677d074bb6c8e8c5e3f73faec0f35a6cf129227ebaChristian Williams   * @param packageName the name of the package, or empty string ({@code ""}) for the top level package
68104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   * @return {@link Config} object for the specified package
69a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @since 3.2
70104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   */
71104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  @Nullable
72104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  private Config buildPackageConfig(String packageName) {
73104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return Config.Implementation.fromProperties(getConfigProperties(packageName));
74104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
75104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
76104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  /**
777d074bb6c8e8c5e3f73faec0f35a6cf129227ebaChristian Williams   * Return a {@link Properties} file for the given package name, or {@code null} if none is available.
78a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   *
79a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams   * @since 3.2
80104b292a58958d310fcb27472fd7e450b3d99946Christian Williams   */
81104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  protected Properties getConfigProperties(String packageName) {
82165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan    List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
83165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan    packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
84165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan    final String resourceName = Join.join("/", packageParts);
85104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
86104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      if (resourceAsStream == null) return null;
87104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      Properties properties = new Properties();
88104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      properties.load(resourceAsStream);
89104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      return properties;
90104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    } catch (IOException e) {
91104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      throw new RuntimeException(e);
92104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
93104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
94104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
9568dc68433d8be4dd7eb7d8a9dda078ad37ce1d16Jonathan Gerrish  @Nonnull @VisibleForTesting
968749eceadefcef06481f51a6915b75ec15aba6b2Christian Williams  List<String> packageHierarchyOf(Class<?> javaClass) {
97b1bfc1aa31f349fc12da4d7f3052880e3e97536eChristian Williams    Package aPackage = javaClass.getPackage();
98b1bfc1aa31f349fc12da4d7f3052880e3e97536eChristian Williams    String testPackageName = aPackage == null ? "" : aPackage.getName();
99104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    List<String> packageHierarchy = new ArrayList<>();
100104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    while (!testPackageName.isEmpty()) {
101104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      packageHierarchy.add(testPackageName);
102104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      int lastDot = testPackageName.lastIndexOf('.');
103104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
104104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
105104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    packageHierarchy.add("");
106104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return packageHierarchy;
107104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
108104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
10968dc68433d8be4dd7eb7d8a9dda078ad37ce1d16Jonathan Gerrish  @Nonnull
110104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  private List<Class> parentClassesFor(Class testClass) {
111104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    List<Class> testClassHierarchy = new ArrayList<>();
112104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    while (testClass != null && !testClass.equals(Object.class)) {
113104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      testClassHierarchy.add(testClass);
114104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      testClass = testClass.getSuperclass();
115104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
116104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return testClassHierarchy;
117104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
118104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
119104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  private Config override(Config config, Config classConfig) {
120104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
121104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
122104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
123104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  @Nullable
124104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  private Config cachedPackageConfig(String packageName) {
125104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    synchronized (packageConfigCache) {
126104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      Config config = packageConfigCache.get(packageName);
127104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      if (config == null && !packageConfigCache.containsKey(packageName)) {
128104b292a58958d310fcb27472fd7e450b3d99946Christian Williams        config = buildPackageConfig(packageName);
129104b292a58958d310fcb27472fd7e450b3d99946Christian Williams        packageConfigCache.put(packageName, config);
130104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      }
131104b292a58958d310fcb27472fd7e450b3d99946Christian Williams      return config;
132104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    }
133104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
134104b292a58958d310fcb27472fd7e450b3d99946Christian Williams
135104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  // visible for testing
136104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  @SuppressWarnings("WeakerAccess")
137104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  InputStream getResourceAsStream(String resourceName) {
138104b292a58958d310fcb27472fd7e450b3d99946Christian Williams    return getClass().getClassLoader().getResourceAsStream(resourceName);
139104b292a58958d310fcb27472fd7e450b3d99946Christian Williams  }
140104b292a58958d310fcb27472fd7e450b3d99946Christian Williams}
141