Merge change 26471 into eclair

* changes:
  Fix the qualifier match algorithm.
This commit is contained in:
Android (Google) Code Review
2009-09-22 22:40:48 -04:00
5 changed files with 195 additions and 65 deletions

View File

@@ -48,6 +48,13 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
private final static int INDEX_VERSION = 13;
private final static int INDEX_COUNT = 14;
/**
* Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
*/
public static int getQualifierCount() {
return INDEX_COUNT;
}
/**
* Sets the config from the qualifiers of a given <var>config</var>.
* @param config
@@ -147,6 +154,16 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
}
}
/**
* Returns a qualifier by its index. The total number of qualifiers can be accessed by
* {@link #getQualifierCount()}.
* @param index the index of the qualifier to return.
* @return the qualifier or null if there are none at the index.
*/
public ResourceQualifier getQualifier(int index) {
return mQualifiers[index];
}
public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
}
@@ -446,7 +463,7 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
}
/**
* Returns whether the configuration match the given reference config.
* Returns whether the configuration is a match for the given reference config.
* <p/>A match means that:
* <ul>
* <li>This config does not use any qualifier not used by the reference config</li>
@@ -454,29 +471,24 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
* the reference config.</li>
* </ul>
* @param referenceConfig The reference configuration to test against.
* @return the number of matching qualifiers or -1 if the configurations are not compatible.
* @return true if the configuration matches.
*/
public int match(FolderConfiguration referenceConfig) {
int matchCount = 0;
public boolean isMatchFor(FolderConfiguration referenceConfig) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
ResourceQualifier testQualifier = mQualifiers[i];
ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
// we only care if testQualifier is non null. If it's null, it's a match but
// without increasing the matchCount.
// we only care if testQualifier is non null.
if (testQualifier != null) {
if (referenceQualifier == null) {
return -1;
} else if (testQualifier.equals(referenceQualifier) == false) {
return -1;
if (referenceQualifier == null) { // reference config doesn't specify anything
// for this qualifier so we refuse it.
return false;
} else if (testQualifier.isMatchFor(referenceQualifier) == false) {
return false;
}
// the qualifier match, increment the count
matchCount++;
}
}
return matchCount;
return true;
}
/**

View File

@@ -141,6 +141,42 @@ public final class KeyboardStateQualifier extends ResourceQualifier {
return false;
}
@Override
public boolean isMatchFor(ResourceQualifier qualifier) {
if (qualifier instanceof KeyboardStateQualifier) {
KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)qualifier;
// special case where EXPOSED can be used for SOFT
if (referenceQualifier.mValue == KeyboardState.SOFT &&
mValue == KeyboardState.EXPOSED) {
return true;
}
return referenceQualifier.mValue == mValue;
}
return false;
}
@Override
public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
if (compareTo == null) {
return true;
}
KeyboardStateQualifier compareQualifier = (KeyboardStateQualifier)compareTo;
KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)reference;
if (referenceQualifier.mValue == KeyboardState.SOFT) { // only case where there could be a
// better qualifier
// only return true if it's a better value.
if (compareQualifier.mValue == KeyboardState.EXPOSED && mValue == KeyboardState.SOFT) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object qualifier) {
if (qualifier instanceof KeyboardStateQualifier) {

View File

@@ -183,6 +183,38 @@ public final class PixelDensityQualifier extends ResourceQualifier {
return false;
}
@Override
public boolean isMatchFor(ResourceQualifier qualifier) {
if (qualifier instanceof PixelDensityQualifier) {
// as long as there's a density qualifier, it's always a match.
// The best match will be found later.
return true;
}
return false;
}
@Override
public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
if (compareTo == null) {
return true;
}
PixelDensityQualifier compareQ = (PixelDensityQualifier)compareTo;
PixelDensityQualifier referenceQ = (PixelDensityQualifier)reference;
if (mValue == referenceQ.mValue && compareQ.mValue != referenceQ.mValue) {
// got exact value, this is the best!
return true;
} else {
// in all case we're going to prefer the higher dpi.
// if reference is high, we want highest dpi.
// if reference is medium, we'll prefer to scale down high dpi, than scale up low dpi
// if reference if low, we'll prefer to scale down high than medium (2:1 over 4:3)
return mValue.mDpiValue > compareQ.mValue.mDpiValue;
}
}
@Override
public boolean equals(Object qualifier) {
if (qualifier instanceof PixelDensityQualifier) {

View File

@@ -62,6 +62,32 @@ public abstract class ResourceQualifier implements Comparable<ResourceQualifier>
*/
public abstract String getFolderSegment(IAndroidTarget target);
/**
* Returns whether the given qualifier is a match for the receiver.
* <p/>The default implementation returns the result of {@link #equals(Object)}.
* <p/>Children class that re-implements this must implement
* {@link #isBetterMatchThan(ResourceQualifier, ResourceQualifier)} too.
* @param qualifier the reference qualifier
* @return true if the receiver is a match.
*/
public boolean isMatchFor(ResourceQualifier qualifier) {
return equals(qualifier);
}
/**
* Returns true if the receiver is a better match for the given <var>reference</var> than
* the given <var>compareTo</var> comparable.
* @param compareTo The {@link ResourceQualifier} to compare to. Can be null, in which
* case the method must return <code>true</code>.
* @param reference The reference qualifier value for which the match is.
* @return true if the receiver is a better match.
*/
public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
// the default is to always return false. This gives less overhead than always returning
// true, as it would only compare same values anyway.
return false;
}
@Override
public String toString() {
return getFolderSegment(null);

View File

@@ -22,6 +22,7 @@ import com.android.ide.eclipse.adt.internal.resources.ResourceType;
import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
import com.android.ide.eclipse.adt.internal.resources.manager.files.IAbstractFolder;
import com.android.layoutlib.api.IResourceValue;
import com.android.layoutlib.utils.ResourceValue;
@@ -505,79 +506,102 @@ public class ProjectResources implements IResourceRepository {
* Returns the best matching {@link Resource}.
* @param resources the list of {@link Resource} to choose from.
* @param referenceConfig the {@link FolderConfiguration} to match.
* @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
*/
private Resource findMatchingConfiguredResource(List<? extends Resource> resources,
FolderConfiguration referenceConfig) {
// look for resources with the maximum number of qualifier match.
int currentMax = -1;
//
// 1: eliminate resources that contradict the reference configuration
// 2: pick next qualifier type
// 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
// 4: eliminate resources that don't use this qualifier.
// 5: if more than one resource left, go back to 2.
//
// The precedence of the qualifiers is more important than the number of qualifiers that
// exactly match the device.
// 1: eliminate resources that contradict
ArrayList<Resource> matchingResources = new ArrayList<Resource>();
for (int i = 0 ; i < resources.size(); i++) {
Resource res = resources.get(i);
int count = res.getConfiguration().match(referenceConfig);
if (count > currentMax) {
matchingResources.clear();
matchingResources.add(res);
currentMax = count;
} else if (count != -1 && count == currentMax) {
if (res.getConfiguration().isMatchFor(referenceConfig)) {
matchingResources.add(res);
}
}
// if we have more than one match, we look for the match with the qualifiers with the
// highest priority.
Resource resMatch = null;
// if there is only one match, just take it
if (matchingResources.size() == 1) {
resMatch = matchingResources.get(0);
} else if (matchingResources.size() > 1) {
// More than one resource with the same number of qualifier match.
// We loop, looking for the resource with the highest priority qualifiers.
ArrayList<Resource> tmpResources = new ArrayList<Resource>();
int startIndex = 0;
while (matchingResources.size() > 1) {
int highest = -1;
for (int i = 0 ; i < matchingResources.size() ; i++) {
Resource folder = matchingResources.get(i);
return matchingResources.get(0);
} else if (matchingResources.size() == 0) {
return null;
}
// get highest priority qualifiers.
int m = folder.getConfiguration().getHighestPriorityQualifier(startIndex);
// 2. Loop on the qualifiers, and eliminate matches
final int count = FolderConfiguration.getQualifierCount();
for (int q = 0 ; q < count ; q++) {
// look to see if one resource has this qualifier.
// At the same time also record the best match value for the qualifier (if applicable).
ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q);
// add to the list if highest.
if (m != -1) {
if (highest == -1 || m == highest) {
tmpResources.add(folder);
highest = m;
} else if (m < highest) { // highest priority == lowest index.
tmpResources.clear();
tmpResources.add(folder);
if (referenceQualifier != null) { // no need to check if it's null, since the loop
// above will have removed the resources anyway.
boolean found = false;
ResourceQualifier bestMatch = null;
for (Resource res : matchingResources) {
ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
if (qualifier != null) {
// set the flag.
found = true;
// now check for a best match.
if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
bestMatch = qualifier;
}
}
}
// at this point, we have a list with 1+ resources that all have the same highest
// priority qualifiers. Go through the list again looking for the next highest
// priority qualifier.
startIndex = highest + 1;
// if a resources as a qualifier at the current index, remove all the resources that
// do not have one.
// If there is one, and we have a bestComparable, also check that it's equal to the
// best comparable.
if (found) {
for (int i = 0 ; i < matchingResources.size(); ) {
Resource res = matchingResources.get(i);
ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
// this should not happen, but it's better to check.
if (matchingResources.size() == tmpResources.size() && highest == -1) {
// this means all the resources match with the same qualifiers
// (highest == -1 means we reached the end of the qualifier list)
// In this case, we arbitrarily take the first resource.
matchingResources.clear();
matchingResources.add(tmpResources.get(0));
} else {
matchingResources.clear();
matchingResources.addAll(tmpResources);
if (qualifier == null) { // no qualifier? remove the resources
matchingResources.remove(res);
} else if (bestMatch != null && bestMatch.equals(qualifier) == false) {
// if there is a best match, only accept the resource if the qualifier
// has the same best value.
matchingResources.remove(res);
} else {
i++;
}
}
// at this point we may have run out of matching resources before going
// through all the qualifiers.
if (matchingResources.size() == 1) {
return matchingResources.get(0);
} else if (matchingResources.size() == 0) {
return null;
}
}
tmpResources.clear();
}
// we should have only one match here.
resMatch = matchingResources.get(0);
}
return resMatch;
// went through all the qualifiers. We should not have more than one
switch (matchingResources.size()) {
case 0:
return null;
case 1:
return matchingResources.get(1);
case 2:
assert false;
}
return null;
}
/**