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