package com.airbnb.epoxy;
import com.squareup.javapoet.ClassName;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
class DataBindingModuleLookup {
private final Elements elements;
private final Types types;
private final ErrorLogger errorLogger;
private final LayoutResourceProcessor resourceProcessor;
DataBindingModuleLookup(Elements elements, Types types, ErrorLogger errorLogger,
LayoutResourceProcessor resourceProcessor) {
this.elements = elements;
this.types = types;
this.errorLogger = errorLogger;
this.resourceProcessor = resourceProcessor;
}
String getModuleName(Element element) {
PackageElement packageOf = elements.getPackageOf(element);
String packageName = packageOf.getQualifiedName().toString();
// First we try to get the module name by looking at what R classes were found when processing
// layout annotations. This may find nothing if no layouts were given as annotation params
String moduleName = getModuleNameViaResources(packageName);
if (moduleName == null) {
// If the first approach fails, we try to guess at the R class for the module and look up
// the class to see if it exists. This can fail if this model's package name does not
// include the module name as a prefix (convention makes this unlikely.)
moduleName = getModuleNameViaGuessing(packageName);
}
if (moduleName == null) {
errorLogger.logError("Could not find module name for DataBinding BR class.");
// Fallback to using the package name so we can at least try to generate and compile something
moduleName = packageName;
}
return moduleName;
}
/**
* Attempts to get the module name of the given package. We can do this because the package name
* of an R class is the module. Generally only one R class is used and we can just use that module
* name, but it is possible to have multiple R classes. In that case we compare the package names
* to find what is the most similar.
* <p>
* We need to get the module name to know the path of the BR class for data binding.
*/
private String getModuleNameViaResources(String packageName) {
List<ClassName> rClasses = resourceProcessor.getRClassNames();
if (rClasses.isEmpty()) {
return packageName;
}
if (rClasses.size() == 1) {
// Common case
return rClasses.get(0).packageName();
}
// Generally the only R class used should be the app's. It is possible to use other R classes
// though, like Android's. In that case we figure out the most likely match by comparing the
// package name.
// For example we might have "com.airbnb.epoxy.R" and "android.R"
String[] packageNames = packageName.split("\\.");
ClassName bestMatch = null;
int bestNumMatches = -1;
for (ClassName rClass : rClasses) {
String[] rModuleNames = rClass.packageName().split("\\.");
int numNameMatches = 0;
for (int i = 0; i < Math.min(packageNames.length, rModuleNames.length); i++) {
if (packageNames[i].equals(rModuleNames[i])) {
numNameMatches++;
} else {
break;
}
}
if (numNameMatches > bestNumMatches) {
bestMatch = rClass;
}
}
return bestMatch.packageName();
}
/**
* Attempts to get the android module that is currently being processed.. We can do this because
* the package name of an R class is the module name. So, we take any element in the module,
* <p>
* We need to get the module name to know the path of the BR class for data binding.
*/
private String getModuleNameViaGuessing(String packageName) {
String[] packageNameParts = packageName.split("\\.");
String moduleName = "";
for (int i = 0; i < packageNameParts.length; i++) {
moduleName += packageNameParts[i];
Element rClass = Utils.getElementByName(moduleName + ".R", elements, types);
if (rClass != null) {
return moduleName;
} else {
moduleName += ".";
}
}
return null;
}
}