package org.robolectric.res;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Android qualifers as defined by https://developer.android.com/guide/topics/resources/providing-resources.html
*/
public class Qualifiers {
// Matches a version qualifier like "v14". Parentheses capture the numeric
// part for easy retrieval with Matcher.group(2).
private static final Pattern SCREEN_WIDTH_PATTERN = Pattern.compile("^w([0-9]+)dp");
private static final Pattern SMALLEST_SCREEN_WIDTH_PATTERN = Pattern.compile("^sw([0-9]+)dp");
private static final Pattern VERSION_QUALIFIER_PATTERN = Pattern.compile("(v)([0-9]+)$");
private static final Pattern SIZE_QUALIFIER_PATTERN = Pattern.compile("(s?[wh])([0-9]+)dp");
private static final Pattern ORIENTATION_QUALIFIER_PATTERN = Pattern.compile("(land|port)");
// Version are matched in the end, and hence have least order
private static final int ORDER_VERSION = 0;
// Various size qualifies, in increasing order of importance.
private static final List<String> INT_QUALIFIERS = Arrays.asList("v", "h", "w", "sh", "sw");
private static final int TOTAL_ORDER_COUNT = INT_QUALIFIERS.size();
private static final Map<String, Qualifiers> sQualifiersCache = new HashMap<>();
private final int[] mWeights = new int[TOTAL_ORDER_COUNT];
// Set of all the qualifiers which need exact matching.
private final List<String> mDefaults = new ArrayList<>();
public boolean matches(Qualifiers other) {
if (!passesRequirements(other)) {
return false;
}
return other.mDefaults.containsAll(mDefaults);
}
public boolean passesRequirements(Qualifiers other) {
for (int i = 0; i < TOTAL_ORDER_COUNT; i++) {
if (other.mWeights[i] != -1 && mWeights[i] != -1 && other.mWeights[i] < mWeights[i]) {
return false;
}
}
return true;
}
public boolean isBetterThan(Qualifiers other, Qualifiers context) {
// Compare the defaults in the order they appear in the context.
for (String qualifier : context.mDefaults) {
if (other.mDefaults.contains(qualifier) ^ mDefaults.contains(qualifier)) {
return mDefaults.contains(qualifier);
}
}
for (int i = TOTAL_ORDER_COUNT - 1; i > ORDER_VERSION; i--) {
if (other.mWeights[i] != mWeights[i]) {
return mWeights[i] > other.mWeights[i];
}
}
// Compare the version only if the context defines a version.
if (context.mWeights[ORDER_VERSION] != -1
&& other.mWeights[ORDER_VERSION] != mWeights[ORDER_VERSION]) {
return mWeights[ORDER_VERSION] > other.mWeights[ORDER_VERSION];
}
// The qualifiers match completely
return false;
}
public static Qualifiers parse(String qualifiersStr) {
synchronized (sQualifiersCache) {
Qualifiers result = sQualifiersCache.get(qualifiersStr);
if (result != null) {
return result;
}
StringTokenizer st = new StringTokenizer(qualifiersStr, "-");
result = new Qualifiers();
// Version qualifiers are also allowed to match when only one of the qualifiers
// defines a version restriction.
result.mWeights[ORDER_VERSION] = -1;
while (st.hasMoreTokens()) {
String qualifier = st.nextToken();
if (qualifier.isEmpty()) {
continue;
}
Matcher m = VERSION_QUALIFIER_PATTERN.matcher(qualifier);
if (!m.find()) {
m = SIZE_QUALIFIER_PATTERN.matcher(qualifier);
if (!m.find()) {
m = null;
}
}
if (m != null) {
int order = INT_QUALIFIERS.indexOf(m.group(1));
if (order == ORDER_VERSION && result.mWeights[ORDER_VERSION] != -1) {
throw new IllegalStateException(
"A resource file was found that had two API level qualifiers: " + qualifiersStr);
}
result.mWeights[order] = Integer.parseInt(m.group(2));
} else {
result.mDefaults.add(qualifier);
}
}
sQualifiersCache.put(qualifiersStr, result);
return result;
}
}
public static int getPlatformVersion(String qualifiers) {
Matcher m = VERSION_QUALIFIER_PATTERN.matcher(qualifiers);
if (m.find()) {
return Integer.parseInt(m.group(2));
}
return -1;
}
public static int getSmallestScreenWidth(String qualifiers) {
for (String qualifier : qualifiers.split("-")) {
Matcher matcher = SMALLEST_SCREEN_WIDTH_PATTERN.matcher(qualifier);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
}
return -1;
}
/*
* If the Config already has a version qualifier, do nothing. Otherwise, add a version
* qualifier for the target api level (which comes from the manifest or Config.sdk()).
*/
public static String addPlatformVersion(String qualifiers, int apiLevel) {
int versionQualifierApiLevel = Qualifiers.getPlatformVersion(qualifiers);
if (versionQualifierApiLevel == -1) {
if (qualifiers.length() > 0) {
qualifiers += "-";
}
qualifiers += "v" + apiLevel;
}
return qualifiers;
}
/*
* If the Config already has a version qualifier, do nothing. Otherwise, add a version
* qualifier for the target api level (which comes from the manifest or Config.sdk()).
*/
public static String addSmallestScreenWidth(String qualifiers, int smallestScreenWidth) {
int qualifiersSmallestScreenWidth = Qualifiers.getSmallestScreenWidth(qualifiers);
if (qualifiersSmallestScreenWidth == -1) {
if (qualifiers.length() > 0) {
qualifiers += "-";
}
qualifiers += "sw" + smallestScreenWidth + "dp";
}
return qualifiers;
}
public static int getScreenWidth(String qualifiers) {
for (String qualifier : qualifiers.split("-")) {
Matcher matcher = SCREEN_WIDTH_PATTERN.matcher(qualifier);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
}
return -1;
}
public static String addScreenWidth(String qualifiers, int screenWidth) {
int qualifiersScreenWidth = Qualifiers.getScreenWidth(qualifiers);
if (qualifiersScreenWidth == -1) {
if (qualifiers.length() > 0) {
qualifiers += "-";
}
qualifiers += "w" + screenWidth + "dp";
}
return qualifiers;
}
public static String getOrientation(String qualifiers) {
for (String qualifier : qualifiers.split("-")) {
Matcher matcher = ORIENTATION_QUALIFIER_PATTERN.matcher(qualifier);
if (matcher.find()) {
return matcher.group(1);
}
}
return null;
}
}