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 
IsValidSymbol(const StringPiece & symbol)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 '_'.
TransformToFieldName(const StringPiece & symbol)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
TransformNestedAttr(const ResourceNameRef & attr_name,const std::string & styleable_class_name,const StringPiece & package_name_to_generate)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 
AddAttributeFormatDoc(AnnotationProcessor * processor,Attribute * attr)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 
JavaClassGenerator(IAaptContext * context,ResourceTable * table,const JavaClassGeneratorOptions & options)187 JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
188                                        ResourceTable* table,
189                                        const JavaClassGeneratorOptions& options)
190     : context_(context), table_(table), options_(options) {}
191 
SkipSymbol(SymbolState state)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.
SkipSymbol(const Maybe<SymbolTable::Symbol> & 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 
operator <(const StyleableAttr & lhs,const StyleableAttr & rhs)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 
ProcessStyleable(const ResourceNameRef & name,const ResourceId & id,const Styleable & styleable,const StringPiece & package_name_to_generate,ClassDefinition * out_class_def,MethodDefinition * out_rewrite_method,std::ostream * out_r_txt)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 
ProcessResource(const ResourceNameRef & name,const ResourceId & id,const ResourceEntry & entry,ClassDefinition * out_class_def,MethodDefinition * out_rewrite_method,std::ostream * out_r_txt)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 
UnmangleResource(const StringPiece & package_name,const StringPiece & package_name_to_generate,const ResourceEntry & entry)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 
ProcessType(const StringPiece & package_name_to_generate,const ResourceTablePackage & package,const ResourceTableType & type,ClassDefinition * out_type_class_def,MethodDefinition * out_rewrite_method_def,std::ostream * out_r_txt)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 
Generate(const StringPiece & package_name_to_generate,std::ostream * out,std::ostream * out_r_txt)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 
AppendJavaDocAnnotations(const std::vector<std::string> & annotations,AnnotationProcessor * processor)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 
Generate(const StringPiece & package_name_to_generate,const StringPiece & out_package_name,std::ostream * out,std::ostream * out_r_txt)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