org.robolectric.annotation.Config Maven / Gradle / Ivy
package org.robolectric.annotation;
import android.app.Application;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
/**
* Configuration settings that can be used on a per-class or per-test basis.
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Config {
/**
* TODO(vnayar): Create named constants for default values instead of magic numbers.
* Array named contants must be avoided in order to dodge a JDK 1.7 bug.
* error: annotation Config is missing value for the attribute <clinit>
* See JDK-8013485.
*/
String NONE = "--none";
String DEFAULT_MANIFEST = "--default";
String DEFAULT_PACKAGE_NAME = "";
String DEFAULT_ABI_SPLIT = "";
String DEFAULT_QUALIFIERS = "";
String DEFAULT_RES_FOLDER = "res";
String DEFAULT_ASSET_FOLDER = "assets";
String DEFAULT_BUILD_FOLDER = "build";
/**
* The Android SDK level to emulate. If not specified, Robolectric defaults to API 16.
* This value will also be set as Build.VERSION.SDK_INT.
*
* @return The Android SDK level to emulate.
*/
int[] sdk() default {}; // DEFAULT_SDK
/**
* The Android manifest file to load; Robolectric will look relative to the current directory.
* Resources and assets will be loaded relative to the manifest.
*
* If not specified, Robolectric defaults to {@code AndroidManifest.xml}.
*
* If your project has no manifest or resources, use {@link Config#NONE}.
*
* @return The Android manifest file to load.
*/
String manifest() default DEFAULT_MANIFEST;
/**
* Reference to the BuildConfig class created by the Gradle build system.
*
* @return Reference to BuildConfig class.
*/
Class> constants() default Void.class; // DEFAULT_CONSTANTS
/**
* The {@link android.app.Application} class to use in the test, this takes precedence over any application
* specified in the AndroidManifest.xml.
*
* @return The {@link android.app.Application} class to use in the test.
*/
Class extends Application> application() default Application.class; // DEFAULT_APPLICATION
/**
* Java package name where the "R.class" file is located. This only needs to be specified if you define
* an {@code applicationId} associated with {@code productFlavors} or specify {@code applicationIdSuffix}
* in your build.gradle.
*
* If not specified, Robolectric defaults to the {@code applicationId}.
*
* @return The java package name for R.class.
*/
String packageName() default DEFAULT_PACKAGE_NAME;
/**
* The ABI split to use when locating resources and AndroidManifest.xml
*
* You do not typically have to set this, unless you are utilizing the ABI split feature
*
* @return The ABI split to test with
*/
String abiSplit() default DEFAULT_ABI_SPLIT;
/**
* Qualifiers for the resource resolution, such as "fr-normal-port-hdpi".
*
* @return Qualifiers used for resource resolution.
*/
String qualifiers() default DEFAULT_QUALIFIERS;
/**
* The directory from which to load resources. This should be relative to the directory containing AndroidManifest.xml.
*
* If not specified, Robolectric defaults to {@code res}.
*
* @return Android resource directory.
*/
String resourceDir() default DEFAULT_RES_FOLDER;
/**
* The directory from which to load assets. This should be relative to the directory containing AndroidManifest.xml.
*
* If not specified, Robolectric defaults to {@code assets}.
*
* @return Android asset directory.
*/
String assetDir() default DEFAULT_ASSET_FOLDER;
/**
* The directory where application files are created during the application build process.
*
* If not specified, Robolectric defaults to {@code build}.
*
* @return Android build directory.
*/
String buildDir() default DEFAULT_BUILD_FOLDER;
/**
* A list of shadow classes to enable, in addition to those that are already present.
*
* @return A list of additional shadow classes to enable.
*/
Class>[] shadows() default {}; // DEFAULT_SHADOWS
/**
* A list of instrumented packages, in addition to those that are already instrumented.
*
* @return A list of additional instrumented packages.
*/
String[] instrumentedPackages() default {}; // DEFAULT_INSTRUMENTED_PACKAGES
/**
* A list of folders containing Android Libraries on which this project depends.
*
* @return A list of Android Libraries.
*/
String[] libraries() default {}; // DEFAULT_LIBRARIES;
class Implementation implements Config {
private final int[] sdk;
private final String manifest;
private final String qualifiers;
private final String resourceDir;
private final String assetDir;
private final String buildDir;
private final String packageName;
private final String abiSplit;
private final Class> constants;
private final Class>[] shadows;
private final String[] instrumentedPackages;
private final Class extends Application> application;
private final String[] libraries;
public static Config fromProperties(Properties properties) {
if (properties == null || properties.size() == 0) return null;
return new Implementation(
parseIntArrayProperty(properties.getProperty("sdk", "")),
properties.getProperty("manifest", DEFAULT_MANIFEST),
properties.getProperty("qualifiers", DEFAULT_QUALIFIERS),
properties.getProperty("packageName", DEFAULT_PACKAGE_NAME),
properties.getProperty("abiSplit", DEFAULT_ABI_SPLIT),
properties.getProperty("resourceDir", DEFAULT_RES_FOLDER),
properties.getProperty("assetDir", DEFAULT_ASSET_FOLDER),
properties.getProperty("buildDir", DEFAULT_BUILD_FOLDER),
parseClasses(properties.getProperty("shadows", "")),
parseStringArrayProperty(properties.getProperty("instrumentedPackages", "")),
parseApplication(properties.getProperty("application", "android.app.Application")),
parseStringArrayProperty(properties.getProperty("libraries", "")),
parseClass(properties.getProperty("constants", ""))
);
}
private static Class> parseClass(String className) {
if (className.isEmpty()) return null;
try {
return Implementation.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load class: " + className);
}
}
private static Class>[] parseClasses(String input) {
if (input.isEmpty()) return new Class[0];
final String[] classNames = input.split("[, ]+");
final Class[] classes = new Class[classNames.length];
for (int i = 0; i < classNames.length; i++) {
classes[i] = parseClass(classNames[i]);
}
return classes;
}
@SuppressWarnings("unchecked")
private static Class parseApplication(String className) {
return (Class) parseClass(className);
}
private static String[] parseStringArrayProperty(String property) {
if (property.isEmpty()) return new String[0];
return property.split("[, ]+");
}
private static int[] parseIntArrayProperty(String property) {
String[] parts = parseStringArrayProperty(property);
int[] result = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = Integer.parseInt(parts[i]);
}
return result;
}
public Implementation(int[] sdk, String manifest, String qualifiers, String packageName, String abiSplit, String resourceDir, String assetDir, String buildDir, Class>[] shadows, String[] instrumentedPackages, Class extends Application> application, String[] libraries, Class> constants) {
this.sdk = sdk;
this.manifest = manifest;
this.qualifiers = qualifiers;
this.packageName = packageName;
this.abiSplit = abiSplit;
this.resourceDir = resourceDir;
this.assetDir = assetDir;
this.buildDir = buildDir;
this.shadows = shadows;
this.instrumentedPackages = instrumentedPackages;
this.application = application;
this.libraries = libraries;
this.constants = constants;
}
public Implementation(Config other) {
this.sdk = other.sdk();
this.manifest = other.manifest();
this.qualifiers = other.qualifiers();
this.packageName = other.packageName();
this.abiSplit = other.abiSplit();
this.resourceDir = other.resourceDir();
this.assetDir = other.assetDir();
this.buildDir = other.buildDir();
this.constants = other.constants();
this.shadows = other.shadows();
this.instrumentedPackages = other.instrumentedPackages();
this.application = other.application();
this.libraries = other.libraries();
}
public Implementation(Config baseConfig, Config overlayConfig) {
this.sdk = pickSdk(baseConfig.sdk(), overlayConfig.sdk(), new int[0]);
this.manifest = pick(baseConfig.manifest(), overlayConfig.manifest(), DEFAULT_MANIFEST);
this.qualifiers = pick(baseConfig.qualifiers(), overlayConfig.qualifiers(), "");
this.packageName = pick(baseConfig.packageName(), overlayConfig.packageName(), "");
this.abiSplit = pick(baseConfig.abiSplit(), overlayConfig.abiSplit(), "");
this.resourceDir = pick(baseConfig.resourceDir(), overlayConfig.resourceDir(), Config.DEFAULT_RES_FOLDER);
this.assetDir = pick(baseConfig.assetDir(), overlayConfig.assetDir(), Config.DEFAULT_ASSET_FOLDER);
this.buildDir = pick(baseConfig.buildDir(), overlayConfig.buildDir(), Config.DEFAULT_BUILD_FOLDER);
this.constants = pick(baseConfig.constants(), overlayConfig.constants(), Void.class);
Set> shadows = new HashSet<>();
shadows.addAll(Arrays.asList(baseConfig.shadows()));
shadows.addAll(Arrays.asList(overlayConfig.shadows()));
this.shadows = shadows.toArray(new Class[shadows.size()]);
Set instrumentedPackages = new HashSet<>();
instrumentedPackages.addAll(Arrays.asList(baseConfig.instrumentedPackages()));
instrumentedPackages.addAll(Arrays.asList(overlayConfig.instrumentedPackages()));
this.instrumentedPackages = instrumentedPackages.toArray(new String[instrumentedPackages.size()]);
this.application = pick(baseConfig.application(), overlayConfig.application(), Application.class);
Set libraries = new HashSet<>();
libraries.addAll(Arrays.asList(baseConfig.libraries()));
libraries.addAll(Arrays.asList(overlayConfig.libraries()));
this.libraries = libraries.toArray(new String[libraries.size()]);
}
private T pick(T baseValue, T overlayValue, T nullValue) {
return overlayValue != null ? (overlayValue.equals(nullValue) ? baseValue : overlayValue) : null;
}
private int[] pickSdk(int[] baseValue, int[] overlayValue, int[] nullValue) {
return Arrays.equals(overlayValue, nullValue) ? baseValue : overlayValue;
}
@Override
public int[] sdk() {
return sdk;
}
@Override
public String manifest() {
return manifest;
}
@Override
public Class> constants() {
return constants;
}
@Override
public Class extends Application> application() {
return application;
}
@Override
public String qualifiers() {
return qualifiers;
}
@Override
public String packageName() {
return packageName;
}
@Override
public String abiSplit() {
return abiSplit;
}
@Override
public String resourceDir() {
return resourceDir;
}
@Override
public String assetDir() {
return assetDir;
}
@Override
public String buildDir() {
return buildDir;
}
@Override
public Class>[] shadows() {
return shadows;
}
@Override
public String[] instrumentedPackages() {
return instrumentedPackages;
}
@Override
public String[] libraries() {
return libraries;
}
@NotNull @Override
public Class extends Annotation> annotationType() {
return Config.class;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy