Home | History | Annotate | Download | only in java
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "java/JavaClassGenerator.h"
     18 
     19 #include <algorithm>
     20 #include <ostream>
     21 #include <set>
     22 #include <sstream>
     23 #include <tuple>
     24 
     25 #include "android-base/errors.h"
     26 #include "android-base/logging.h"
     27 #include "android-base/stringprintf.h"
     28 #include "androidfw/StringPiece.h"
     29 
     30 #include "NameMangler.h"
     31 #include "Resource.h"
     32 #include "ResourceTable.h"
     33 #include "ResourceValues.h"
     34 #include "SdkConstants.h"
     35 #include "ValueVisitor.h"
     36 #include "java/AnnotationProcessor.h"
     37 #include "java/ClassDefinition.h"
     38 #include "process/SymbolTable.h"
     39 
     40 using android::StringPiece;
     41 using android::base::StringPrintf;
     42 
     43 namespace aapt {
     44 
     45 static const std::set<StringPiece> sJavaIdentifiers = {
     46     "abstract",   "assert",       "boolean",   "break",      "byte",
     47     "case",       "catch",        "char",      "class",      "const",
     48     "continue",   "default",      "do",        "double",     "else",
     49     "enum",       "extends",      "final",     "finally",    "float",
     50     "for",        "goto",         "if",        "implements", "import",
     51     "instanceof", "int",          "interface", "long",       "native",
     52     "new",        "package",      "private",   "protected",  "public",
     53     "return",     "short",        "static",    "strictfp",   "super",
     54     "switch",     "synchronized", "this",      "throw",      "throws",
     55     "transient",  "try",          "void",      "volatile",   "while",
     56     "true",       "false",        "null"};
     57 
     58 static bool IsValidSymbol(const StringPiece& symbol) {
     59   return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
     60 }
     61 
     62 // Java symbols can not contain . or -, but those are valid in a resource name.
     63 // Replace those with '_'.
     64 static std::string TransformToFieldName(const StringPiece& symbol) {
     65   std::string output = symbol.to_string();
     66   for (char& c : output) {
     67     if (c == '.' || c == '-') {
     68       c = '_';
     69     }
     70   }
     71   return output;
     72 }
     73 
     74 // Transforms an attribute in a styleable to the Java field name:
     75 //
     76 // <declare-styleable name="Foo">
     77 //   <attr name="android:bar" />
     78 //   <attr name="bar" />
     79 // </declare-styleable>
     80 //
     81 // Foo_android_bar
     82 // Foo_bar
     83 static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
     84                                        const std::string& styleable_class_name,
     85                                        const StringPiece& package_name_to_generate) {
     86   std::string output = styleable_class_name;
     87 
     88   // We may reference IDs from other packages, so prefix the entry name with
     89   // the package.
     90   if (!attr_name.package.empty() &&
     91       package_name_to_generate != attr_name.package) {
     92     output += "_" + TransformToFieldName(attr_name.package);
     93   }
     94   output += "_" + TransformToFieldName(attr_name.entry);
     95   return output;
     96 }
     97 
     98 static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
     99   const uint32_t type_mask = attr->type_mask;
    100   if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
    101     processor->AppendComment(
    102         "<p>May be a reference to another resource, in the form\n"
    103         "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a "
    104         "theme\n"
    105         "attribute in the form\n"
    106         "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
    107   }
    108 
    109   if (type_mask & android::ResTable_map::TYPE_STRING) {
    110     processor->AppendComment(
    111         "<p>May be a string value, using '\\\\;' to escape characters such as\n"
    112         "'\\\\n' or '\\\\uxxxx' for a unicode character;");
    113   }
    114 
    115   if (type_mask & android::ResTable_map::TYPE_INTEGER) {
    116     processor->AppendComment(
    117         "<p>May be an integer value, such as \"<code>100</code>\".");
    118   }
    119 
    120   if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
    121     processor->AppendComment(
    122         "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
    123         "\"<code>false</code>\".");
    124   }
    125 
    126   if (type_mask & android::ResTable_map::TYPE_COLOR) {
    127     processor->AppendComment(
    128         "<p>May be a color value, in the form of "
    129         "\"<code>#<i>rgb</i></code>\",\n"
    130         "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n"
    131         "\"<code>#<i>aarrggbb</i></code>\".");
    132   }
    133 
    134   if (type_mask & android::ResTable_map::TYPE_FLOAT) {
    135     processor->AppendComment(
    136         "<p>May be a floating point value, such as \"<code>1.2</code>\".");
    137   }
    138 
    139   if (type_mask & android::ResTable_map::TYPE_DIMENSION) {
    140     processor->AppendComment(
    141         "<p>May be a dimension value, which is a floating point number "
    142         "appended with a\n"
    143         "unit such as \"<code>14.5sp</code>\".\n"
    144         "Available units are: px (pixels), dp (density-independent pixels),\n"
    145         "sp (scaled pixels based on preferred font size), in (inches), and\n"
    146         "mm (millimeters).");
    147   }
    148 
    149   if (type_mask & android::ResTable_map::TYPE_FRACTION) {
    150     processor->AppendComment(
    151         "<p>May be a fractional value, which is a floating point number "
    152         "appended with\n"
    153         "either % or %p, such as \"<code>14.5%</code>\".\n"
    154         "The % suffix always means a percentage of the base size;\n"
    155         "the optional %p suffix provides a size relative to some parent "
    156         "container.");
    157   }
    158 
    159   if (type_mask &
    160       (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
    161     if (type_mask & android::ResTable_map::TYPE_FLAGS) {
    162       processor->AppendComment(
    163           "<p>Must be one or more (separated by '|') of the following "
    164           "constant values.</p>");
    165     } else {
    166       processor->AppendComment(
    167           "<p>Must be one of the following constant values.</p>");
    168     }
    169 
    170     processor->AppendComment(
    171         "<table>\n<colgroup align=\"left\" />\n"
    172         "<colgroup align=\"left\" />\n"
    173         "<colgroup align=\"left\" />\n"
    174         "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
    175     for (const Attribute::Symbol& symbol : attr->symbols) {
    176       std::stringstream line;
    177       line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
    178            << "<td>" << std::hex << symbol.value << std::dec << "</td>"
    179            << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
    180            << "</td></tr>";
    181       processor->AppendComment(line.str());
    182     }
    183     processor->AppendComment("</table>");
    184   }
    185 }
    186 
    187 JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
    188                                        ResourceTable* table,
    189                                        const JavaClassGeneratorOptions& options)
    190     : context_(context), table_(table), options_(options) {}
    191 
    192 bool JavaClassGenerator::SkipSymbol(SymbolState state) {
    193   switch (options_.types) {
    194     case JavaClassGeneratorOptions::SymbolTypes::kAll:
    195       return false;
    196     case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
    197       return state == SymbolState::kUndefined;
    198     case JavaClassGeneratorOptions::SymbolTypes::kPublic:
    199       return state != SymbolState::kPublic;
    200   }
    201   return true;
    202 }
    203 
    204 // Whether or not to skip writing this symbol.
    205 bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) {
    206   return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
    207                      !symbol.value().is_public);
    208 }
    209 
    210 struct StyleableAttr {
    211   const Reference* attr_ref = nullptr;
    212   std::string field_name;
    213   Maybe<SymbolTable::Symbol> symbol;
    214 };
    215 
    216 static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
    217   const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
    218   const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0));
    219   if (lhs_id < rhs_id) {
    220     return true;
    221   } else if (lhs_id > rhs_id) {
    222     return false;
    223   } else {
    224     return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
    225   }
    226 }
    227 
    228 void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
    229                                           const Styleable& styleable,
    230                                           const StringPiece& package_name_to_generate,
    231                                           ClassDefinition* out_class_def,
    232                                           MethodDefinition* out_rewrite_method,
    233                                           std::ostream* out_r_txt) {
    234   const std::string array_field_name = TransformToFieldName(name.entry);
    235   std::unique_ptr<ResourceArrayMember> array_def =
    236       util::make_unique<ResourceArrayMember>(array_field_name);
    237 
    238   // The array must be sorted by resource ID.
    239   std::vector<StyleableAttr> sorted_attributes;
    240   sorted_attributes.reserve(styleable.entries.size());
    241   for (const auto& attr : styleable.entries) {
    242     // If we are not encoding final attributes, the styleable entry may have no
    243     // ID if we are building a static library.
    244     CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
    245     CHECK(bool(attr.name)) << "no name set for Styleable entry";
    246 
    247     // We will need the unmangled, transformed name in the comments and the field,
    248     // so create it once and cache it in this StyleableAttr data structure.
    249     StyleableAttr styleable_attr;
    250     styleable_attr.attr_ref = &attr;
    251 
    252     // The field name for this attribute is prefixed by the name of this styleable and
    253     // the package it comes from.
    254     styleable_attr.field_name =
    255         TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
    256 
    257     // Look up the symbol so that we can write out in the comments what are possible legal values
    258     // for this attribute.
    259     const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr);
    260     if (symbol && symbol->attribute) {
    261       // Copy the symbol data structure because the returned instance can be destroyed.
    262       styleable_attr.symbol = *symbol;
    263     }
    264     sorted_attributes.push_back(std::move(styleable_attr));
    265   }
    266 
    267   // Sort the attributes by ID.
    268   std::sort(sorted_attributes.begin(), sorted_attributes.end());
    269 
    270   // Build the JavaDoc comment for the Styleable array. This has references to child attributes
    271   // and what possible values can be used for them.
    272   const size_t attr_count = sorted_attributes.size();
    273   if (attr_count > 0) {
    274     std::stringstream styleable_comment;
    275     if (!styleable.GetComment().empty()) {
    276       styleable_comment << styleable.GetComment() << "\n";
    277     } else {
    278       // Apply a default intro comment if the styleable has no comments of its own.
    279       styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
    280     }
    281 
    282     styleable_comment << "<p>Includes the following attributes:</p>\n"
    283                          "<table>\n"
    284                          "<colgroup align=\"left\" />\n"
    285                          "<colgroup align=\"left\" />\n"
    286                          "<tr><th>Attribute</th><th>Description</th></tr>\n";
    287 
    288     // Build the table of attributes with their links and names.
    289     for (const StyleableAttr& entry : sorted_attributes) {
    290       if (SkipSymbol(entry.symbol)) {
    291         continue;
    292       }
    293 
    294       StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
    295       if (attr_comment_line.contains("@removed")) {
    296         // Removed attributes are public but hidden from the documentation, so
    297         // don't emit them as part of the class documentation.
    298         continue;
    299       }
    300 
    301       const ResourceName& attr_name = entry.attr_ref->name.value();
    302       styleable_comment << "<tr><td>";
    303       styleable_comment << "<code>{@link #" << entry.field_name << " "
    304                         << (!attr_name.package.empty()
    305                                 ? attr_name.package
    306                                 : context_->GetCompilationPackage())
    307                         << ":" << attr_name.entry << "}</code>";
    308       styleable_comment << "</td>";
    309 
    310       styleable_comment << "<td>";
    311 
    312       // Only use the comment up until the first '.'. This is to stay compatible with
    313       // the way old AAPT did it (presumably to keep it short and to avoid including
    314       // annotations like @hide which would affect this Styleable).
    315       auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.');
    316       if (iter != attr_comment_line.end()) {
    317         attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
    318       }
    319       styleable_comment << attr_comment_line << "</td></tr>\n";
    320     }
    321     styleable_comment << "</table>\n";
    322 
    323     // Generate the @see lines for each attribute.
    324     for (const StyleableAttr& entry : sorted_attributes) {
    325       if (SkipSymbol(entry.symbol)) {
    326         continue;
    327       }
    328       styleable_comment << "@see #" << entry.field_name << "\n";
    329     }
    330 
    331     array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
    332   }
    333 
    334   if (out_r_txt != nullptr) {
    335     *out_r_txt << "int[] styleable " << array_field_name << " {";
    336   }
    337 
    338   // Add the ResourceIds to the array member.
    339   for (size_t i = 0; i < attr_count; i++) {
    340     const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
    341     array_def->AddElement(id);
    342 
    343     if (out_r_txt != nullptr) {
    344       if (i != 0) {
    345         *out_r_txt << ",";
    346       }
    347       *out_r_txt << " " << id;
    348     }
    349   }
    350 
    351   if (out_r_txt != nullptr) {
    352     *out_r_txt << " }\n";
    353   }
    354 
    355   // Add the Styleable array to the Styleable class.
    356   out_class_def->AddMember(std::move(array_def));
    357 
    358   // Now we emit the indices into the array.
    359   for (size_t i = 0; i < attr_count; i++) {
    360     const StyleableAttr& styleable_attr = sorted_attributes[i];
    361     if (SkipSymbol(styleable_attr.symbol)) {
    362       continue;
    363     }
    364 
    365     StringPiece comment = styleable_attr.attr_ref->GetComment();
    366     if (styleable_attr.symbol.value().attribute && comment.empty()) {
    367       comment = styleable_attr.symbol.value().attribute->GetComment();
    368     }
    369 
    370     if (comment.contains("@removed")) {
    371       // Removed attributes are public but hidden from the documentation, so
    372       // don't emit them as part of the class documentation.
    373       continue;
    374     }
    375 
    376     const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
    377 
    378     StringPiece package_name = attr_name.package;
    379     if (package_name.empty()) {
    380       package_name = context_->GetCompilationPackage();
    381     }
    382 
    383     std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>(
    384         sorted_attributes[i].field_name, static_cast<uint32_t>(i));
    385 
    386     AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
    387 
    388     if (!comment.empty()) {
    389       attr_processor->AppendComment("<p>\n@attr description");
    390       attr_processor->AppendComment(comment);
    391     } else {
    392       std::stringstream default_comment;
    393       default_comment << "<p>This symbol is the offset where the "
    394                       << "{@link " << package_name << ".R.attr#"
    395                       << TransformToFieldName(attr_name.entry) << "}\n"
    396                       << "attribute's value can be found in the "
    397                       << "{@link #" << array_field_name << "} array.";
    398       attr_processor->AppendComment(default_comment.str());
    399     }
    400 
    401     attr_processor->AppendNewLine();
    402     AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
    403     attr_processor->AppendNewLine();
    404     attr_processor->AppendComment(
    405         StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
    406 
    407     if (out_r_txt != nullptr) {
    408       *out_r_txt << StringPrintf("int styleable %s %d\n", sorted_attributes[i].field_name.data(),
    409                                  (int)i);
    410     }
    411 
    412     out_class_def->AddMember(std::move(index_member));
    413   }
    414 
    415   // If there is a rewrite method to generate, add the statements that rewrite package IDs
    416   // for this styleable.
    417   if (out_rewrite_method != nullptr) {
    418     out_rewrite_method->AppendStatement(
    419         StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
    420     out_rewrite_method->AppendStatement(
    421         StringPrintf("  if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
    422     out_rewrite_method->AppendStatement(
    423         StringPrintf("    styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
    424                      array_field_name.data(), array_field_name.data()));
    425     out_rewrite_method->AppendStatement("  }");
    426     out_rewrite_method->AppendStatement("}");
    427   }
    428 }
    429 
    430 void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
    431                                          const ResourceEntry& entry, ClassDefinition* out_class_def,
    432                                          MethodDefinition* out_rewrite_method,
    433                                          std::ostream* out_r_txt) {
    434   ResourceId real_id = id;
    435   if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
    436       id.package_id() > kAppPackageId) {
    437     real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
    438   }
    439 
    440   const std::string field_name = TransformToFieldName(name.entry);
    441   std::unique_ptr<ResourceMember> resource_member =
    442       util::make_unique<ResourceMember>(field_name, real_id);
    443 
    444   // Build the comments and annotations for this entry.
    445   AnnotationProcessor* processor = resource_member->GetCommentBuilder();
    446 
    447   // Add the comments from any <public> tags.
    448   if (entry.symbol_status.state != SymbolState::kUndefined) {
    449     processor->AppendComment(entry.symbol_status.comment);
    450   }
    451 
    452   // Add the comments from all configurations of this entry.
    453   for (const auto& config_value : entry.values) {
    454     processor->AppendComment(config_value->value->GetComment());
    455   }
    456 
    457   // If this is an Attribute, append the format Javadoc.
    458   if (!entry.values.empty()) {
    459     if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
    460       // We list out the available values for the given attribute.
    461       AddAttributeFormatDoc(processor, attr);
    462     }
    463   }
    464 
    465   out_class_def->AddMember(std::move(resource_member));
    466 
    467   if (out_r_txt != nullptr) {
    468     *out_r_txt << "int " << name.type << " " << field_name << " " << real_id << "\n";
    469   }
    470 
    471   if (out_rewrite_method != nullptr) {
    472     const StringPiece& type_str = ToString(name.type);
    473     out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
    474                                                      type_str.data(), field_name.data(),
    475                                                      type_str.data(), field_name.data()));
    476   }
    477 }
    478 
    479 Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
    480                                                         const StringPiece& package_name_to_generate,
    481                                                         const ResourceEntry& entry) {
    482   if (SkipSymbol(entry.symbol_status.state)) {
    483     return {};
    484   }
    485 
    486   std::string unmangled_package;
    487   std::string unmangled_name = entry.name;
    488   if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
    489     // The entry name was mangled, and we successfully unmangled it.
    490     // Check that we want to emit this symbol.
    491     if (package_name != unmangled_package) {
    492       // Skip the entry if it doesn't belong to the package we're writing.
    493       return {};
    494     }
    495   } else if (package_name_to_generate != package_name) {
    496     // We are processing a mangled package name,
    497     // but this is a non-mangled resource.
    498     return {};
    499   }
    500   return {std::move(unmangled_name)};
    501 }
    502 
    503 bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
    504                                      const ResourceTablePackage& package,
    505                                      const ResourceTableType& type,
    506                                      ClassDefinition* out_type_class_def,
    507                                      MethodDefinition* out_rewrite_method_def,
    508                                      std::ostream* out_r_txt) {
    509   for (const auto& entry : type.entries) {
    510     const Maybe<std::string> unmangled_name =
    511         UnmangleResource(package.name, package_name_to_generate, *entry);
    512     if (!unmangled_name) {
    513       continue;
    514     }
    515 
    516     // Create an ID if there is one (static libraries don't need one).
    517     ResourceId id;
    518     if (package.id && type.id && entry->id) {
    519       id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
    520     }
    521 
    522     // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
    523     const ResourceNameRef resource_name(
    524         package_name_to_generate,
    525         type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
    526         unmangled_name.value());
    527 
    528     // Check to see if the unmangled name is a valid Java name (not a keyword).
    529     if (!IsValidSymbol(unmangled_name.value())) {
    530       std::stringstream err;
    531       err << "invalid symbol name '" << resource_name << "'";
    532       error_ = err.str();
    533       return false;
    534     }
    535 
    536     if (resource_name.type == ResourceType::kStyleable) {
    537       CHECK(!entry->values.empty());
    538 
    539       const Styleable* styleable =
    540           static_cast<const Styleable*>(entry->values.front()->value.get());
    541 
    542       ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
    543                        out_rewrite_method_def, out_r_txt);
    544     } else {
    545       ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
    546                       out_r_txt);
    547     }
    548   }
    549   return true;
    550 }
    551 
    552 bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out,
    553                                   std::ostream* out_r_txt) {
    554   return Generate(package_name_to_generate, package_name_to_generate, out);
    555 }
    556 
    557 static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
    558                                      AnnotationProcessor* processor) {
    559   for (const std::string& annotation : annotations) {
    560     std::string proper_annotation = "@";
    561     proper_annotation += annotation;
    562     processor->AppendComment(proper_annotation);
    563   }
    564 }
    565 
    566 bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
    567                                   const StringPiece& out_package_name, std::ostream* out,
    568                                   std::ostream* out_r_txt) {
    569   ClassDefinition r_class("R", ClassQualifier::kNone, true);
    570   std::unique_ptr<MethodDefinition> rewrite_method;
    571 
    572   // Generate an onResourcesLoaded() callback if requested.
    573   if (options_.rewrite_callback_options) {
    574     rewrite_method =
    575         util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
    576     for (const std::string& package_to_callback :
    577          options_.rewrite_callback_options.value().packages_to_callback) {
    578       rewrite_method->AppendStatement(
    579           StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
    580     }
    581   }
    582 
    583   for (const auto& package : table_->packages) {
    584     for (const auto& type : package->types) {
    585       if (type->type == ResourceType::kAttrPrivate) {
    586         // We generate these as part of the kAttr type, so skip them here.
    587         continue;
    588       }
    589 
    590       // Stay consistent with AAPT and generate an empty type class if the R class
    591       // is public.
    592       const bool force_creation_if_empty =
    593           (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
    594 
    595       std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
    596           ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty);
    597       if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
    598                        rewrite_method.get(), out_r_txt)) {
    599         return false;
    600       }
    601 
    602       if (type->type == ResourceType::kAttr) {
    603         // Also include private attributes in this same class.
    604         const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
    605         if (priv_type) {
    606           if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
    607                            rewrite_method.get(), out_r_txt)) {
    608             return false;
    609           }
    610         }
    611       }
    612 
    613       if (type->type == ResourceType::kStyleable &&
    614           options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
    615         // When generating a public R class, we don't want Styleable to be part
    616         // of the API. It is only emitted for documentation purposes.
    617         class_def->GetCommentBuilder()->AppendComment("@doconly");
    618       }
    619 
    620       AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
    621 
    622       r_class.AddMember(std::move(class_def));
    623     }
    624   }
    625 
    626   if (rewrite_method != nullptr) {
    627     r_class.AddMember(std::move(rewrite_method));
    628   }
    629 
    630   AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
    631 
    632   if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) {
    633     return false;
    634   }
    635 
    636   out->flush();
    637 
    638   if (out_r_txt != nullptr) {
    639     out_r_txt->flush();
    640 
    641     if (!*out_r_txt) {
    642       error_ = android::base::SystemErrorCodeToString(errno);
    643       return false;
    644     }
    645   }
    646 
    647   return true;
    648 }
    649 
    650 }  // namespace aapt
    651