use of com.android.resources.ResourceType in project bazel by bazelbuild.
the class RClassGenerator method write.
/** Builds the bytecode and writes out the R.class file, and R$inner.class files. */
public void write() throws IOException {
Iterable<String> folders = PACKAGE_SPLITTER.split(packageName);
Path packageDir = outFolder;
for (String folder : folders) {
packageDir = packageDir.resolve(folder);
}
// At least create the outFolder that was requested. However, if there are no symbols, don't
// create the R.class and inner class files (no need to have an empty class).
Files.createDirectories(packageDir);
if (initializers.isEmpty()) {
return;
}
Path rClassFile = packageDir.resolve(SdkConstants.FN_COMPILED_RESOURCE_CLASS);
String packageWithSlashes = packageName.replaceAll("\\.", "/");
String rClassName = packageWithSlashes.isEmpty() ? "R" : (packageWithSlashes + "/R");
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classWriter.visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, rClassName, null, /* signature */
SUPER_CLASS, null);
classWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null);
writeConstructor(classWriter);
// Build the R.class w/ the inner classes, then later build the individual R$inner.class.
for (ResourceType resourceType : initializers.keySet()) {
String innerClassName = rClassName + "$" + resourceType;
classWriter.visitInnerClass(innerClassName, rClassName, resourceType.toString(), Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);
}
classWriter.visitEnd();
Files.write(rClassFile, classWriter.toByteArray());
// Now generate the R$inner.class files.
for (Map.Entry<ResourceType, List<FieldInitializer>> entry : initializers.entrySet()) {
writeInnerClass(entry.getValue(), packageDir, rClassName, entry.getKey().toString());
}
}
use of com.android.resources.ResourceType in project bazel by bazelbuild.
the class ResourceUsageAnalyzer method removeUnused.
/**
* Remove resources (already identified by {@link #shrink(Path)}).
*
* <p>This task will copy all remaining used resources over from the full resource directory to a
* new reduced resource directory and removes unused values from all value xml files.
*
* @param destination directory to copy resources into; if null, delete resources in place
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
private void removeUnused(Path destination) throws IOException, ParserConfigurationException, SAXException {
// should always call analyze() first
assert unused != null;
// *4: account for some resource folder repetition
int resourceCount = unused.size() * 4;
Set<File> skip = Sets.newHashSetWithExpectedSize(resourceCount);
Set<File> rewrite = Sets.newHashSetWithExpectedSize(resourceCount);
Set<Resource> deleted = Sets.newHashSetWithExpectedSize(resourceCount);
for (Resource resource : unused) {
if (resource.declarations != null) {
for (File file : resource.declarations) {
String folder = file.getParentFile().getName();
ResourceFolderType folderType = ResourceFolderType.getFolderType(folder);
if (folderType != null && folderType != ResourceFolderType.VALUES) {
List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
ResourceType type = types.get(0);
assert type != ResourceType.ID : folderType;
Resource fileResource = model.getResource(type, LintUtils.getBaseName(file.getName()));
// considered uses and would otherwise cause deletion of the file.
if (fileResource == null || fileResource.equals(resource)) {
logger.fine("Deleted unused file " + file + " for resource " + resource);
assert skip != null;
skip.add(file);
deleted.add(resource);
}
} else {
// Can't delete values immediately; there can be many resources
// in this file, so we have to process them all
rewrite.add(file);
}
}
} else {
// Not declared anywhere; mark as deleted. Covers the case of inline resources.
// https://developer.android.com/guide/topics/resources/complex-xml-resources.html
deleted.add(resource);
}
}
// Special case the base values.xml folder
File values = new File(mergedResourceDir.toFile(), FD_RES_VALUES + File.separatorChar + "values.xml");
if (values.exists()) {
rewrite.add(values);
}
Map<File, String> rewritten = Maps.newHashMapWithExpectedSize(rewrite.size());
rewriteXml(rewrite, rewritten, deleted);
// TODO(apell): The graph traversal does not mark IDs as reachable or not, so they cannot be
// accurately removed from public.xml, but the definitions may be deleted if they occur in
// other files. IDs should be added to values.xml so that there are no declarations in
// public.xml without definitions.
File publicXml = new File(mergedResourceDir.toFile(), FD_RES_VALUES + File.separatorChar + "public.xml");
createStubIds(values, rewritten, publicXml);
trimPublicResources(publicXml, deleted, rewritten);
filteredCopy(mergedResourceDir.toFile(), destination, skip, rewritten);
}
use of com.android.resources.ResourceType in project bazel by bazelbuild.
the class ResourceUsageAnalyzer method keepPossiblyReferencedResources.
private void keepPossiblyReferencedResources() {
if ((!foundGetIdentifier && !foundWebContent) || strings == null) {
// to worry about string references to resources
return;
}
if (!model.isSafeMode()) {
// explicitly mark them as kept if necessary instead
return;
}
List<String> sortedStrings = new ArrayList<String>(strings);
Collections.sort(sortedStrings);
logger.fine("android.content.res.Resources#getIdentifier present: " + foundGetIdentifier);
logger.fine("Web content present: " + foundWebContent);
logger.fine("Referenced Strings:");
for (String string : sortedStrings) {
string = string.trim().replace("\n", "\\n");
if (string.length() > 40) {
string = string.substring(0, 37) + "...";
} else if (string.isEmpty()) {
continue;
}
logger.fine(" " + string);
}
int shortest = Integer.MAX_VALUE;
Set<String> names = Sets.newHashSetWithExpectedSize(50);
for (Resource resource : model.getResources()) {
String name = resource.name;
names.add(name);
int length = name.length();
if (length < shortest) {
shortest = length;
}
}
for (String string : strings) {
if (string.length() < shortest) {
continue;
}
// (4) If foundWebContent is true, look for android_res/ URL strings as well
if (foundWebContent) {
Resource resource = model.getResourceFromFilePath(string);
if (resource != null) {
ResourceUsageModel.markReachable(resource);
continue;
} else {
int start = 0;
int slash = string.lastIndexOf('/');
if (slash != -1) {
start = slash + 1;
}
int dot = string.indexOf('.', start);
String name = string.substring(start, dot != -1 ? dot : string.length());
if (names.contains(name)) {
for (Map<String, Resource> map : model.getResourceMaps()) {
resource = map.get(name);
if (resource != null) {
logger.fine(String.format("Marking %s used because it matches string pool constant %s", resource, string));
}
ResourceUsageModel.markReachable(resource);
}
}
}
}
// Look for normal getIdentifier resource URLs
int n = string.length();
boolean justName = true;
boolean formatting = false;
boolean haveSlash = false;
for (int i = 0; i < n; i++) {
char c = string.charAt(i);
if (c == '/') {
haveSlash = true;
justName = false;
} else if (c == '.' || c == ':' || c == '%') {
justName = false;
if (c == '%') {
formatting = true;
}
} else if (!Character.isJavaIdentifierPart(c)) {
// the {@link #referencedString} method
assert false : string;
break;
}
}
String name;
if (justName) {
// Check name (below)
name = string;
// getResources().getIdentifier("ic_video_codec_" + codecName, "drawable", ...)
for (Resource resource : model.getResources()) {
if (resource.name.startsWith(name)) {
logger.fine(String.format("Marking %s used because its prefix matches string pool constant %s", resource, string));
ResourceUsageModel.markReachable(resource);
}
}
} else if (!haveSlash) {
if (formatting) {
// int res = getContext().getResources().getIdentifier(name, "drawable", ...)
try {
Pattern pattern = Pattern.compile(convertFormatStringToRegexp(string));
for (Resource resource : model.getResources()) {
if (pattern.matcher(resource.name).matches()) {
logger.fine(String.format("Marking %s used because it format-string matches string pool constant %s", resource, string));
ResourceUsageModel.markReachable(resource);
}
}
} catch (PatternSyntaxException ignored) {
// Might not have been a formatting string after all!
}
}
//noinspection UnnecessaryContinue
continue;
} else {
// Try to pick out the resource name pieces; if we can find the
// resource type unambiguously; if not, just match on names
int slash = string.indexOf('/');
// checked with haveSlash above
assert slash != -1;
name = string.substring(slash + 1);
if (name.isEmpty() || !names.contains(name)) {
continue;
}
// See if have a known specific resource type
if (slash > 0) {
int colon = string.indexOf(':');
String typeName = string.substring(colon != -1 ? colon + 1 : 0, slash);
ResourceType type = ResourceType.getEnum(typeName);
if (type == null) {
continue;
}
Resource resource = model.getResource(type, name);
if (resource != null) {
logger.fine(String.format("Marking %s used because it matches string pool constant %s", resource, string));
}
ResourceUsageModel.markReachable(resource);
continue;
}
// fall through and check the name
}
if (names.contains(name)) {
for (Map<String, Resource> map : model.getResourceMaps()) {
Resource resource = map.get(name);
if (resource != null) {
logger.fine(String.format("Marking %s used because it matches string pool constant %s", resource, string));
}
ResourceUsageModel.markReachable(resource);
}
} else if (Character.isDigit(name.charAt(0))) {
// "android.resource://com.android.alarmclock/2130837524".
try {
int id = Integer.parseInt(name);
if (id != 0) {
ResourceUsageModel.markReachable(model.getResource(id));
}
} catch (NumberFormatException e) {
// pass
}
}
}
}
use of com.android.resources.ResourceType in project bazel by bazelbuild.
the class ResourceUsageAnalyzer method parseResourceTxtFile.
private void parseResourceTxtFile(Path rTxt, Set<String> resourcePackages) throws IOException {
BufferedReader reader = java.nio.file.Files.newBufferedReader(rTxt, UTF_8);
String line;
while ((line = reader.readLine()) != null) {
String[] tokens = line.split(" ");
ResourceType type = ResourceType.getEnum(tokens[1]);
for (String resourcePackage : resourcePackages) {
String owner = resourcePackage.replace('.', '/') + "/R$" + type.getName();
Pair<ResourceType, Map<String, String>> pair = resourceObfuscation.get(owner);
if (pair == null) {
Map<String, String> nameMap = Maps.newHashMap();
pair = Pair.of(type, nameMap);
}
resourceObfuscation.put(owner, pair);
}
if (type == ResourceType.STYLEABLE) {
if (tokens[0].equals("int[]")) {
model.addResource(ResourceType.DECLARE_STYLEABLE, tokens[2], null);
} else {
// TODO(jongerrish): Implement stripping of styleables.
}
} else {
model.addResource(type, tokens[2], tokens[3]);
}
}
}
use of com.android.resources.ResourceType in project bazel by bazelbuild.
the class ResourceUsageAnalyzer method trimPublicResources.
/**
* Remove public definitions of unused resources.
*/
private void trimPublicResources(File publicXml, Set<Resource> deleted, Map<File, String> rewritten) throws IOException, ParserConfigurationException, SAXException {
if (publicXml.exists()) {
String xml = rewritten.get(publicXml);
if (xml == null) {
xml = Files.toString(publicXml, UTF_8);
}
Document document = XmlUtils.parseDocument(xml, true);
Element root = document.getDocumentElement();
if (root != null && TAG_RESOURCES.equals(root.getTagName())) {
NodeList children = root.getChildNodes();
for (int i = children.getLength() - 1; i >= 0; i--) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element resourceElement = (Element) child;
ResourceType type = ResourceType.getEnum(resourceElement.getAttribute(ATTR_TYPE));
String name = resourceElement.getAttribute(ATTR_NAME);
if (type != null && name != null) {
Resource resource = model.getResource(type, name);
if (resource != null && deleted.contains(resource)) {
root.removeChild(child);
}
}
}
}
}
String formatted = XmlPrettyPrinter.prettyPrint(document, xml.endsWith("\n"));
rewritten.put(publicXml, formatted);
}
}
Aggregations