AAPT2: Add descriptions of Attributes in Styleables for R.java

Change-Id: I69e7b73cbdfe4baf502348397435c501ae29ff5e
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index b100e84..9704d970 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -81,6 +81,12 @@
 
 // Recursively adds resources to the ResourceTable.
 static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
+    StringPiece16 trimmedComment = util::trimWhitespace(res->comment);
+    if (trimmedComment.size() != res->comment.size()) {
+        // Only if there was a change do we re-assign.
+        res->comment = trimmedComment.toString();
+    }
+
     if (res->symbolState) {
         Symbol symbol;
         symbol.state = res->symbolState.value();
diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
index 2b24544..d09a485 100644
--- a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
+++ b/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml
@@ -15,8 +15,13 @@
 -->
 
 <resources>
+    <!-- An attribute from StaticLibOne -->
     <attr name="StaticLibOne_attr" format="string" />
 
     <string name="Foo">Foo</string>
     <string name="Foo" product="tablet">Bar</string>
+
+    <declare-styleable name="Widget">
+        <attr name="StaticLibOne_attr" />
+    </declare-styleable>
 </resources>
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 9c25d4e..496e92e 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -38,7 +38,7 @@
         mComment << "/**";
     }
 
-    mComment << "\n" << " * " << std::move(comment);
+    mComment << "\n * " << std::move(comment);
 }
 
 void AnnotationProcessor::appendComment(const StringPiece16& comment) {
@@ -60,6 +60,10 @@
     }
 }
 
+void AnnotationProcessor::appendNewLine() {
+    mComment << "\n *";
+}
+
 void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) {
     if (mHasComments) {
         std::string result = mComment.str();
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index e7f2be0..fadf584 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -61,6 +61,8 @@
     void appendComment(const StringPiece16& comment);
     void appendComment(const StringPiece& comment);
 
+    void appendNewLine();
+
     /**
      * Writes the comments and annotations to the stream, with the given prefix before each line.
      */
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 1076ffe..01330dc 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -23,6 +23,7 @@
 #include "java/AnnotationProcessor.h"
 #include "java/ClassDefinitionWriter.h"
 #include "java/JavaClassGenerator.h"
+#include "process/SymbolTable.h"
 #include "util/StringPiece.h"
 
 #include <algorithm>
@@ -33,8 +34,9 @@
 
 namespace aapt {
 
-JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
-        mTable(table), mOptions(options) {
+JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* table,
+                                       const JavaClassGeneratorOptions& options) :
+        mContext(context), mTable(table), mOptions(options) {
 }
 
 static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
@@ -103,6 +105,85 @@
     return output;
 }
 
+static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
+    const uint32_t typeMask = attr->typeMask;
+    if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
+        processor->appendComment(
+                "<p>May be a reference to another resource, in the form\n"
+                        "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
+                        "attribute in the form\n"
+                        "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_STRING) {
+        processor->appendComment(
+                "<p>May be a string value, using '\\\\;' to escape characters such as\n"
+                        "'\\\\n' or '\\\\uxxxx' for a unicode character;");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+        processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        processor->appendComment(
+                "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+                        "\"<code>false</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_COLOR) {
+        processor->appendComment(
+                "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
+                        "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+                        "\"<code>#<i>aarrggbb</i></code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FLOAT) {
+        processor->appendComment(
+                "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
+        processor->appendComment(
+                "<p>May be a dimension value, which is a floating point number appended with a\n"
+                        "unit such as \"<code>14.5sp</code>\".\n"
+                        "Available units are: px (pixels), dp (density-independent pixels),\n"
+                        "sp (scaled pixels based on preferred font size), in (inches), and\n"
+                        "mm (millimeters).");
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FRACTION) {
+        processor->appendComment(
+                "<p>May be a fractional value, which is a floating point number appended with\n"
+                        "either % or %p, such as \"<code>14.5%</code>\".\n"
+                        "The % suffix always means a percentage of the base size;\n"
+                        "the optional %p suffix provides a size relative to some parent container.");
+    }
+
+    if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+        if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+            processor->appendComment(
+                    "<p>Must be one or more (separated by '|') of the following "
+                            "constant values.</p>");
+        } else {
+            processor->appendComment("<p>Must be one of the following constant values.</p>");
+        }
+
+        processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
+                                         "<colgroup align=\"left\" />\n"
+                                         "<colgroup align=\"left\" />\n"
+                                         "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
+        for (const Attribute::Symbol& symbol : attr->symbols) {
+            std::stringstream line;
+            line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
+            << "<td>" << std::hex << symbol.value << std::dec << "</td>"
+            << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
+            processor->appendComment(line.str());
+        }
+        processor->appendComment("</table>");
+    }
+}
+
 bool JavaClassGenerator::skipSymbol(SymbolState state) {
     switch (mOptions.types) {
     case JavaClassGeneratorOptions::SymbolTypes::kAll:
@@ -117,6 +198,7 @@
 
 struct StyleableAttr {
     const Reference* attrRef;
+    std::shared_ptr<Attribute> attribute;
     std::string fieldName;
 };
 
@@ -148,8 +230,29 @@
         assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
         assert(attr.name && "no name set for Styleable entry");
 
-        sortedAttributes.emplace_back(StyleableAttr{
-                &attr, transformNestedAttr(attr.name.value(), className, packageNameToGenerate) });
+        StyleableAttr styleableAttr = {};
+        styleableAttr.attrRef = &attr;
+        styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className,
+                                                      packageNameToGenerate);
+
+        Reference mangledReference;
+        mangledReference.id = attr.id;
+        mangledReference.name = attr.name;
+        if (mangledReference.name.value().package.empty()) {
+            mangledReference.name.value().package = mContext->getCompilationPackage();
+        }
+
+        if (Maybe<ResourceName> mangledName =
+                mContext->getNameMangler()->mangleName(mangledReference.name.value())) {
+            mangledReference.name = mangledName;
+        }
+
+        const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference(
+                mangledReference);
+        if (symbol) {
+            styleableAttr.attribute = symbol->attribute;
+        }
+        sortedAttributes.push_back(std::move(styleableAttr));
     }
     std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr);
 
@@ -159,16 +262,34 @@
         // Build the comment string for the Styleable. It includes details about the
         // child attributes.
         std::stringstream styleableComment;
-        styleableComment << "Attributes that can be used with a " << className << ".\n";
-        styleableComment << "<table>\n"
+        if (!styleable->getComment().empty()) {
+            styleableComment << styleable->getComment() << "\n";
+        } else {
+            styleableComment << "Attributes that can be used with a " << className << ".\n";
+        }
+        styleableComment <<
+                "<p>Includes the following attributes:</p>\n"
+                "<table>\n"
                 "<colgroup align=\"left\" />\n"
-                "<colgroup align=\"left\">\n"
+                "<colgroup align=\"left\" />\n"
                 "<tr><th>Attribute</th><th>Description</th></tr>\n";
+
         for (const auto& entry : sortedAttributes) {
             const ResourceName& attrName = entry.attrRef->name.value();
-            styleableComment << "<tr><td><code>{@link #" << entry.fieldName << " "
-                    << attrName.package << ":" << attrName.entry
-                    << "}</code></td><td></td></tr>\n";
+            styleableComment << "<tr><td>";
+            styleableComment << "<code>{@link #"
+                             << entry.fieldName << " "
+                             << (!attrName.package.empty()
+                                    ? attrName.package : mContext->getCompilationPackage())
+                             << ":" << attrName.entry
+                             << "}</code>";
+            styleableComment << "</td>";
+
+            styleableComment << "<td>";
+            if (entry.attribute) {
+                styleableComment << entry.attribute->getComment();
+            }
+            styleableComment << "</td></tr>\n";
         }
         styleableComment << "</table>\n";
         for (const auto& entry : sortedAttributes) {
@@ -189,96 +310,45 @@
 
     // Now we emit the indices into the array.
     for (size_t i = 0; i < attrCount; i++) {
-        const ResourceName& attrName = sortedAttributes[i].attrRef->name.value();
+        const StyleableAttr& styleableAttr = sortedAttributes[i];
+        const ResourceName& attrName = styleableAttr.attrRef->name.value();
+
+        StringPiece16 packageName = attrName.package;
+        if (packageName.empty()) {
+            packageName = mContext->getCompilationPackage();
+        }
 
         AnnotationProcessor attrProcessor;
-        std::stringstream doclavaComments;
-        doclavaComments << "@attr name ";
-        if (!attrName.package.empty()) {
-            doclavaComments << attrName.package << ":";
+
+        StringPiece16 comment = styleableAttr.attrRef->getComment();
+        if (styleableAttr.attribute && comment.empty()) {
+            comment = styleableAttr.attribute->getComment();
         }
-        doclavaComments << attrName.entry;
-        attrProcessor.appendComment(doclavaComments.str());
-        outClassDef->addIntMember(sortedAttributes[i].fieldName, &attrProcessor, i);
-    }
-}
 
-static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
-    const uint32_t typeMask = attr->typeMask;
-    if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
-        processor->appendComment(
-                "<p>May be a reference to another resource, in the form\n"
-                "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
-                "attribute in the form\n"
-                "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_STRING) {
-        processor->appendComment(
-                "<p>May be a string value, using '\\\\;' to escape characters such as\n"
-                "'\\\\n' or '\\\\uxxxx' for a unicode character;");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
-        processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
-        processor->appendComment(
-                "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
-                "\"<code>false</code>\".");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_COLOR) {
-        processor->appendComment(
-                "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
-                "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
-                "\"<code>#<i>aarrggbb</i></code>\".");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_FLOAT) {
-        processor->appendComment(
-                "<p>May be a floating point value, such as \"<code>1.2</code>\".");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
-        processor->appendComment(
-                "<p>May be a dimension value, which is a floating point number appended with a\n"
-                "unit such as \"<code>14.5sp</code>\".\n"
-                "Available units are: px (pixels), dp (density-independent pixels),\n"
-                "sp (scaled pixels based on preferred font size), in (inches), and\n"
-                "mm (millimeters).");
-    }
-
-    if (typeMask & android::ResTable_map::TYPE_FRACTION) {
-        processor->appendComment(
-                "<p>May be a fractional value, which is a floating point number appended with\n"
-                "either % or %p, such as \"<code>14.5%</code>\".\n"
-                "The % suffix always means a percentage of the base size;\n"
-                "the optional %p suffix provides a size relative to some parent container.");
-    }
-
-    if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
-        if (typeMask & android::ResTable_map::TYPE_FLAGS) {
-            processor->appendComment(
-                    "<p>Must be one or more (separated by '|') of the following "
-                    "constant values.</p>");
+        if (!comment.empty()) {
+            attrProcessor.appendComment("<p>\n@attr description");
+            attrProcessor.appendComment(comment);
         } else {
-            processor->appendComment("<p>Must be one of the following constant values.</p>");
+            std::stringstream defaultComment;
+            defaultComment
+                    << "<p>This symbol is the offset where the "
+                    << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n"
+                    << "attribute's value can be found in the "
+                    << "{@link #" << className << "} array.";
+            attrProcessor.appendComment(defaultComment.str());
         }
 
-        processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
-                                 "<colgroup align=\"left\" />\n"
-                                 "<colgroup align=\"left\" />\n"
-                                 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
-        for (const Attribute::Symbol& symbol : attr->symbols) {
-            std::stringstream line;
-            line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
-                 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
-                 << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
-            processor->appendComment(line.str());
+        attrProcessor.appendNewLine();
+
+        if (styleableAttr.attribute) {
+            addAttributeFormatDoc(&attrProcessor, styleableAttr.attribute.get());
+            attrProcessor.appendNewLine();
         }
-        processor->appendComment("</table>");
+
+        std::stringstream doclavaName;
+        doclavaName << "@attr name " << packageName << ":" << attrName.entry;;
+        attrProcessor.appendComment(doclavaName.str());
+        outClassDef->addIntMember(sortedAttributes[i].fieldName, &attrProcessor, i);
     }
 }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 023d6d6..7e46f8c9 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -19,7 +19,7 @@
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-
+#include "process/IResourceTableConsumer.h"
 #include "util/StringPiece.h"
 
 #include <ostream>
@@ -51,7 +51,8 @@
  */
 class JavaClassGenerator {
 public:
-    JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options);
+    JavaClassGenerator(IAaptContext* context, ResourceTable* table,
+                       const JavaClassGeneratorOptions& options);
 
     /*
      * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
@@ -82,6 +83,7 @@
 
     bool skipSymbol(SymbolState state);
 
+    IAaptContext* mContext;
     ResourceTable* mTable;
     JavaClassGeneratorOptions mOptions;
     std::string mError;
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 63d38a8..4f041b8 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -15,11 +15,9 @@
  */
 
 #include "java/JavaClassGenerator.h"
+#include "test/Test.h"
 #include "util/Util.h"
 
-#include "test/Builders.h"
-
-#include <gtest/gtest.h>
 #include <sstream>
 #include <string>
 
@@ -31,7 +29,11 @@
             .addSimple(u"@android:id/class", ResourceId(0x01020000))
             .build();
 
-    JavaClassGenerator generator(table.get(), {});
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
 
     std::stringstream out;
     EXPECT_FALSE(generator.generate(u"android", &out));
@@ -48,7 +50,11 @@
                               .build())
             .build();
 
-    JavaClassGenerator generator(table.get(), {});
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
 
     std::stringstream out;
     EXPECT_TRUE(generator.generate(u"android", &out));
@@ -72,7 +78,11 @@
             .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
             .build();
 
-    JavaClassGenerator generator(table.get(), {});
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
     std::stringstream out;
     ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
 
@@ -90,7 +100,11 @@
             .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
             .build();
 
-    JavaClassGenerator generator(table.get(), {});
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
     std::stringstream out;
     ASSERT_TRUE(generator.generate(u"android", &out));
 
@@ -110,10 +124,15 @@
             .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
             .build();
 
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+
     JavaClassGeneratorOptions options;
     options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
     {
-        JavaClassGenerator generator(table.get(), options);
+        JavaClassGenerator generator(context.get(), table.get(), options);
         std::stringstream out;
         ASSERT_TRUE(generator.generate(u"android", &out));
         std::string output = out.str();
@@ -124,7 +143,7 @@
 
     options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
     {
-        JavaClassGenerator generator(table.get(), options);
+        JavaClassGenerator generator(context.get(), table.get(), options);
         std::stringstream out;
         ASSERT_TRUE(generator.generate(u"android", &out));
         std::string output = out.str();
@@ -135,7 +154,7 @@
 
     options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
     {
-        JavaClassGenerator generator(table.get(), options);
+        JavaClassGenerator generator(context.get(), table.get(), options);
         std::stringstream out;
         ASSERT_TRUE(generator.generate(u"android", &out));
         std::string output = out.str();
@@ -189,7 +208,11 @@
                                   .build())
                 .build();
 
-    JavaClassGenerator generator(table.get(), {});
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
 
     std::stringstream out;
     EXPECT_TRUE(generator.generate(u"android", &out));
@@ -207,8 +230,11 @@
     test::getValue<Id>(table.get(), u"@android:id/foo")
             ->setComment(std::u16string(u"This is a comment\n@deprecated"));
 
-    JavaClassGenerator generator(table.get(), {});
-
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
+    JavaClassGenerator generator(context.get(), table.get(), {});
     std::stringstream out;
     ASSERT_TRUE(generator.generate(u"android", &out));
     std::string actual = out.str();
@@ -241,10 +267,13 @@
                       std::unique_ptr<Styleable>(styleable.clone(nullptr)))
             .build();
 
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+            .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+            .build();
     JavaClassGeneratorOptions options;
     options.useFinal = false;
-    JavaClassGenerator generator(table.get(), options);
-
+    JavaClassGenerator generator(context.get(), table.get(), options);
     std::stringstream out;
     ASSERT_TRUE(generator.generate(u"android", &out));
     std::string actual = out.str();
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 5003d96..d04ef18 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -738,7 +738,7 @@
             return false;
         }
 
-        JavaClassGenerator generator(table, javaOptions);
+        JavaClassGenerator generator(mContext, table, javaOptions);
         if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
             mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
             return false;
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index a8f9bfe..eaaf06f 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -51,6 +51,11 @@
             // doesn't support unique_ptr.
             std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release());
             mCache.put(name, sharedSymbol);
+
+            if (sharedSymbol->id) {
+                // The symbol has an ID, so we can also cache this!
+                mIdCache.put(sharedSymbol->id.value(), sharedSymbol);
+            }
             return sharedSymbol.get();
         }
     }
@@ -76,6 +81,25 @@
     return nullptr;
 }
 
+const SymbolTable::Symbol* SymbolTable::findByReference(const Reference& ref) {
+    // First try the ID. This is because when we lookup by ID, we only fill in the ID cache.
+    // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed
+    // ID lookup, then a successfull name lookup. Subsequent look ups will hit immediately
+    // because the ID is cached too.
+    //
+    // If we looked up by name first, a cache miss would mean we failed to lookup by name, then
+    // succeeded to lookup by ID. Subsequent lookups will miss then hit.
+    const SymbolTable::Symbol* symbol = nullptr;
+    if (ref.id) {
+        symbol = findById(ref.id.value());
+    }
+
+    if (ref.name && !symbol) {
+        symbol = findByName(ref.name.value());
+    }
+    return symbol;
+}
+
 std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::findByName(
         const ResourceName& name) {
     Maybe<ResourceTable::SearchResult> result = mTable->findResource(name);
@@ -102,7 +126,7 @@
         if (configValue) {
             // This resource has an Attribute.
             if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
-                symbol->attribute = util::make_unique<Attribute>(*attr);
+                symbol->attribute = std::make_shared<Attribute>(*attr);
             } else {
                 return {};
             }
@@ -133,7 +157,7 @@
     // Check to see if it is an attribute.
     for (size_t i = 0; i < (size_t) count; i++) {
         if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
-            s->attribute = util::make_unique<Attribute>(false);
+            s->attribute = std::make_shared<Attribute>(false);
             s->attribute->typeMask = entry[i].map.value.data;
             break;
         }
@@ -272,4 +296,15 @@
     return {};
 }
 
+std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByReference(
+        const Reference& ref) {
+    // AssetManager always prefers IDs.
+    if (ref.id) {
+        return findById(ref.id.value());
+    } else if (ref.name) {
+        return findByName(ref.name.value());
+    }
+    return {};
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 8ea1c75..0a6a4a5 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -52,7 +52,7 @@
 public:
     struct Symbol {
         Maybe<ResourceId> id;
-        std::unique_ptr<Attribute> attribute;
+        std::shared_ptr<Attribute> attribute;
         bool isPublic;
     };
 
@@ -69,6 +69,12 @@
     const Symbol* findByName(const ResourceName& name);
     const Symbol* findById(ResourceId id);
 
+    /**
+     * Let's the ISymbolSource decide whether looking up by name or ID is faster, if both
+     * are available.
+     */
+    const Symbol* findByReference(const Reference& ref);
+
 private:
     std::vector<std::unique_ptr<ISymbolSource>> mSources;
 
@@ -90,6 +96,18 @@
 
     virtual std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) = 0;
     virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0;
+
+    /**
+     * Default implementation tries the name if it exists, else the ID.
+     */
+    virtual std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) {
+        if (ref.name) {
+            return findByName(ref.name.value());
+        } else if (ref.id) {
+            return findById(ref.id.value());
+        }
+        return {};
+    }
 };
 
 /**
@@ -122,6 +140,7 @@
 
     std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override;
     std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override;
+    std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) override;
 
 private:
     android::AssetManager mAssets;