blob: 6e73b017cce2363ff038b01c48f34fb16584b96f [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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
Adam Lesinskica5638f2015-10-21 14:42:43 -070017#include "java/JavaClassGenerator.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080018
Adam Lesinskica2fc352015-04-03 12:08:26 -070019#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080020#include <ostream>
21#include <set>
22#include <sstream>
23#include <tuple>
24
Adam Lesinski418763f2017-04-11 17:36:53 -070025#include "android-base/errors.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070026#include "android-base/logging.h"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080027#include "android-base/stringprintf.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080028#include "androidfw/StringPiece.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070029
30#include "NameMangler.h"
31#include "Resource.h"
32#include "ResourceTable.h"
33#include "ResourceValues.h"
Adam Lesinski1e4b0e52017-04-27 15:01:10 -070034#include "SdkConstants.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070035#include "ValueVisitor.h"
36#include "java/AnnotationProcessor.h"
37#include "java/ClassDefinition.h"
38#include "process/SymbolTable.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080039
Adam Lesinskia693c4a2017-11-09 11:29:39 -080040using ::aapt::text::Printer;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000041using ::android::OutputStream;
Adam Lesinskia693c4a2017-11-09 11:29:39 -080042using ::android::StringPiece;
43using ::android::base::StringPrintf;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070044
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080045namespace aapt {
46
Adam Lesinskid0f116b2016-07-08 15:00:32 -070047static const std::set<StringPiece> sJavaIdentifiers = {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070048 "abstract", "assert", "boolean", "break", "byte",
49 "case", "catch", "char", "class", "const",
50 "continue", "default", "do", "double", "else",
51 "enum", "extends", "final", "finally", "float",
52 "for", "goto", "if", "implements", "import",
53 "instanceof", "int", "interface", "long", "native",
54 "new", "package", "private", "protected", "public",
55 "return", "short", "static", "strictfp", "super",
56 "switch", "synchronized", "this", "throw", "throws",
57 "transient", "try", "void", "volatile", "while",
58 "true", "false", "null"};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080059
Yurii Zubrytskyia5775142022-11-02 17:49:49 -070060static bool IsValidSymbol(StringPiece symbol) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070061 return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080062}
63
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080064// Java symbols can not contain . or -, but those are valid in a resource name.
65// Replace those with '_'.
Yurii Zubrytskyia5775142022-11-02 17:49:49 -070066std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) {
67 std::string output(symbol);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070068 for (char& c : output) {
69 if (c == '.' || c == '-') {
70 c = '_';
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080071 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070072 }
73 return output;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080074}
75
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080076// Transforms an attribute in a styleable to the Java field name:
77//
78// <declare-styleable name="Foo">
79// <attr name="android:bar" />
80// <attr name="bar" />
81// </declare-styleable>
82//
83// Foo_android_bar
84// Foo_bar
85static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
86 const std::string& styleable_class_name,
Yurii Zubrytskyia5775142022-11-02 17:49:49 -070087 StringPiece package_name_to_generate) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070088 std::string output = styleable_class_name;
Adam Lesinski74605cd2016-03-03 15:39:50 -080089
Adam Lesinskice5e56e2016-10-21 17:56:45 -070090 // We may reference IDs from other packages, so prefix the entry name with
91 // the package.
92 if (!attr_name.package.empty() &&
93 package_name_to_generate != attr_name.package) {
Adam Koskidc21dea2017-07-21 10:55:27 -070094 output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070095 }
Adam Koskidc21dea2017-07-21 10:55:27 -070096 output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070097 return output;
Adam Lesinski74605cd2016-03-03 15:39:50 -080098}
99
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800100static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700101 const uint32_t type_mask = attr->type_mask;
102 if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
103 processor->AppendComment(
104 "<p>May be a reference to another resource, in the form\n"
105 "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a "
106 "theme\n"
107 "attribute in the form\n"
108 "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
109 }
110
111 if (type_mask & android::ResTable_map::TYPE_STRING) {
112 processor->AppendComment(
113 "<p>May be a string value, using '\\\\;' to escape characters such as\n"
114 "'\\\\n' or '\\\\uxxxx' for a unicode character;");
115 }
116
117 if (type_mask & android::ResTable_map::TYPE_INTEGER) {
118 processor->AppendComment(
119 "<p>May be an integer value, such as \"<code>100</code>\".");
120 }
121
122 if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
123 processor->AppendComment(
124 "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
125 "\"<code>false</code>\".");
126 }
127
128 if (type_mask & android::ResTable_map::TYPE_COLOR) {
129 processor->AppendComment(
130 "<p>May be a color value, in the form of "
131 "\"<code>#<i>rgb</i></code>\",\n"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800132 "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700133 "\"<code>#<i>aarrggbb</i></code>\".");
134 }
135
136 if (type_mask & android::ResTable_map::TYPE_FLOAT) {
137 processor->AppendComment(
138 "<p>May be a floating point value, such as \"<code>1.2</code>\".");
139 }
140
141 if (type_mask & android::ResTable_map::TYPE_DIMENSION) {
142 processor->AppendComment(
143 "<p>May be a dimension value, which is a floating point number "
144 "appended with a\n"
145 "unit such as \"<code>14.5sp</code>\".\n"
146 "Available units are: px (pixels), dp (density-independent pixels),\n"
147 "sp (scaled pixels based on preferred font size), in (inches), and\n"
148 "mm (millimeters).");
149 }
150
151 if (type_mask & android::ResTable_map::TYPE_FRACTION) {
152 processor->AppendComment(
153 "<p>May be a fractional value, which is a floating point number "
154 "appended with\n"
155 "either % or %p, such as \"<code>14.5%</code>\".\n"
156 "The % suffix always means a percentage of the base size;\n"
157 "the optional %p suffix provides a size relative to some parent "
158 "container.");
159 }
160
161 if (type_mask &
162 (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
163 if (type_mask & android::ResTable_map::TYPE_FLAGS) {
164 processor->AppendComment(
165 "<p>Must be one or more (separated by '|') of the following "
166 "constant values.</p>");
167 } else {
168 processor->AppendComment(
169 "<p>Must be one of the following constant values.</p>");
Adam Lesinski76565542016-03-10 21:55:04 -0800170 }
171
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 processor->AppendComment(
173 "<table>\n<colgroup align=\"left\" />\n"
174 "<colgroup align=\"left\" />\n"
175 "<colgroup align=\"left\" />\n"
176 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
177 for (const Attribute::Symbol& symbol : attr->symbols) {
178 std::stringstream line;
179 line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
180 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
181 << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
182 << "</td></tr>";
Mark Punzalan5596a612024-01-19 00:06:38 -0800183 // add_api_annotations is false since we don't want any annotations
184 // (e.g., "@deprecated")/ found in the enum/flag values to be propagated
185 // up to the attribute.
186 processor->AppendComment(line.str(), /*add_api_annotations=*/false);
Adam Lesinski76565542016-03-10 21:55:04 -0800187 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700188 processor->AppendComment("</table>");
189 }
Adam Lesinski76565542016-03-10 21:55:04 -0800190}
191
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700192JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
193 ResourceTable* table,
194 const JavaClassGeneratorOptions& options)
195 : context_(context), table_(table), options_(options) {}
196
Adam Lesinski71be7052017-12-12 16:48:07 -0800197bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700198 switch (options_.types) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700199 case JavaClassGeneratorOptions::SymbolTypes::kAll:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700200 return false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700201 case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
Adam Lesinski71be7052017-12-12 16:48:07 -0800202 return level == Visibility::Level::kUndefined;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700203 case JavaClassGeneratorOptions::SymbolTypes::kPublic:
Adam Lesinski71be7052017-12-12 16:48:07 -0800204 return level != Visibility::Level::kPublic;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700205 }
206 return true;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700207}
208
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800209// Whether or not to skip writing this symbol.
Ryan Mitchell4382e442021-07-14 12:53:01 -0700210bool JavaClassGenerator::SkipSymbol(const std::optional<SymbolTable::Symbol>& symbol) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800211 return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
212 !symbol.value().is_public);
213}
214
Adam Lesinski74605cd2016-03-03 15:39:50 -0800215struct StyleableAttr {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800216 const Reference* attr_ref = nullptr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700217 std::string field_name;
Ryan Mitchell4382e442021-07-14 12:53:01 -0700218 std::optional<SymbolTable::Symbol> symbol;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800219};
220
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800221static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700222 const ResourceId lhs_id = lhs.attr_ref->id.value_or(ResourceId(0));
223 const ResourceId rhs_id = rhs.attr_ref->id.value_or(ResourceId(0));
Clark DuVall8f51d6b2020-04-22 13:19:28 -0700224 if (lhs_id == rhs_id) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700225 return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
226 }
Clark DuVall8f51d6b2020-04-22 13:19:28 -0700227 return cmp_ids_dynamic_after_framework(lhs_id, rhs_id);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700228}
229
Ryan Mitchell5855de72021-02-24 14:39:13 -0800230static FieldReference GetRFieldReference(const ResourceName& name,
231 StringPiece fallback_package_name) {
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700232 const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
Ryan Mitchell5855de72021-02-24 14:39:13 -0800233 const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700234 return FieldReference(
235 StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
Ryan Mitchell5855de72021-02-24 14:39:13 -0800236}
237
238bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800239 const Styleable& styleable,
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700240 StringPiece package_name_to_generate,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800241 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700242 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800243 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800244 const std::string array_field_name = TransformToFieldName(name.entry);
245 std::unique_ptr<ResourceArrayMember> array_def =
246 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700247
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800248 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800250 sorted_attributes.reserve(styleable.entries.size());
251 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700252 // If we are not encoding final attributes, the styleable entry may have no
253 // ID if we are building a static library.
254 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
255 CHECK(bool(attr.name)) << "no name set for Styleable entry";
256
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800257 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800259 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700260 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700261
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800262 // The field name for this attribute is prefixed by the name of this styleable and
263 // the package it comes from.
264 styleable_attr.field_name =
265 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700266
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700267 Reference ref = attr;
268 if (attr.name.value().package.empty()) {
269
270 // If the resource does not have a package name, set the package to the unmangled package name
271 // of the styleable declaration because attributes without package names would have been
272 // declared in the same package as the styleable.
273 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
274 ref.name.value().entry);
275 }
276
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800277 // Look up the symbol so that we can write out in the comments what are possible legal values
278 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700279 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
280
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700281 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800282 // Copy the symbol data structure because the returned instance can be destroyed.
283 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700284 }
285 sorted_attributes.push_back(std::move(styleable_attr));
286 }
287
288 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800289 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700290
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800291 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
292 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700293 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000294 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700295 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800296 if (!styleable.GetComment().empty()) {
297 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800298 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800299 // Apply a default intro comment if the styleable has no comments of its own.
300 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800301 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700302
303 styleable_comment << "<p>Includes the following attributes:</p>\n"
304 "<table>\n"
305 "<colgroup align=\"left\" />\n"
306 "<colgroup align=\"left\" />\n"
307 "<tr><th>Attribute</th><th>Description</th></tr>\n";
308
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800309 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
310 // them as part of the class documentation.
311 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
312 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
313 documentation_attrs.end(),
314 [&](StyleableAttr entry) -> bool {
Ryan Mitchell4e19c482019-12-06 12:20:21 -0800315 if (SkipSymbol(entry.symbol)) {
316 return true;
317 }
318 const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700319 return attr_comment_line.find("@removed") != std::string::npos ||
320 attr_comment_line.find("@hide") != std::string::npos;
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800321 });
322 documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800324 // Build the table of attributes with their links and names.
325 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700327 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
328 << (!attr_name.package.empty() ? attr_name.package
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700329 : package_name_to_generate)
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700330 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700331
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800332 // Only use the comment up until the first '.'. This is to stay compatible with
333 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700334 // annotations like @hide which would affect this Styleable).
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800335 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700336 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
337 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700338 }
339 styleable_comment << "</table>\n";
340
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800341 // Generate the @see lines for each attribute.
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800342 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700343 styleable_comment << "@see #" << entry.field_name << "\n";
344 }
345
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800346 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700347 }
348
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800349 if (r_txt_printer != nullptr) {
350 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700351 }
352
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700353 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700354 for (size_t i = 0; i < attr_count; i++) {
Ryan Mitchell5855de72021-02-24 14:39:13 -0800355 const StyleableAttr& attr = sorted_attributes[i];
356 std::string r_txt_contents;
357 if (attr.symbol && attr.symbol.value().is_dynamic) {
358 if (!attr.attr_ref->name) {
359 error_ = "unable to determine R.java field name of dynamic resource";
360 return false;
361 }
362
363 const FieldReference field_name =
364 GetRFieldReference(attr.attr_ref->name.value(), package_name_to_generate);
365 array_def->AddElement(field_name);
366 r_txt_contents = field_name.ref;
367 } else {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700368 const ResourceId attr_id = attr.attr_ref->id.value_or(ResourceId(0));
Ryan Mitchell5855de72021-02-24 14:39:13 -0800369 array_def->AddElement(attr_id);
370 r_txt_contents = to_string(attr_id);
371 }
Adam Lesinski418763f2017-04-11 17:36:53 -0700372
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800373 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700374 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800375 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700376 }
Ryan Mitchell5855de72021-02-24 14:39:13 -0800377 r_txt_printer->Print(" ").Print(r_txt_contents);
Adam Lesinski418763f2017-04-11 17:36:53 -0700378 }
379 }
380
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800381 if (r_txt_printer != nullptr) {
382 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700383 }
384
385 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800386 if (out_class_def != nullptr) {
387 out_class_def->AddMember(std::move(array_def));
388 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700389
390 // Now we emit the indices into the array.
391 for (size_t i = 0; i < attr_count; i++) {
392 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800393 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700394 continue;
395 }
396
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000397 if (out_class_def != nullptr) {
398 StringPiece comment = styleable_attr.attr_ref->GetComment();
399 if (styleable_attr.symbol.value().attribute && comment.empty()) {
400 comment = styleable_attr.symbol.value().attribute->GetComment();
401 }
402
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700403 if (comment.find("@removed") != std::string::npos) {
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000404 // Removed attributes are public but hidden from the documentation, so
405 // don't emit them as part of the class documentation.
406 continue;
407 }
408
409 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
410
411 StringPiece package_name = attr_name.package;
412 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700413 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000414 }
415
416 std::unique_ptr<IntMember> index_member =
417 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
418
419 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
420
421 if (!comment.empty()) {
422 attr_processor->AppendComment("<p>\n@attr description");
423 attr_processor->AppendComment(comment);
424 } else {
425 std::stringstream default_comment;
426 default_comment << "<p>This symbol is the offset where the "
427 << "{@link " << package_name << ".R.attr#"
428 << TransformToFieldName(attr_name.entry) << "}\n"
429 << "attribute's value can be found in the "
430 << "{@link #" << array_field_name << "} array.";
431 attr_processor->AppendComment(default_comment.str());
432 }
433
434 attr_processor->AppendNewLine();
435 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
436 attr_processor->AppendNewLine();
437 attr_processor->AppendComment(
438 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
439
440 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700441 }
442
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800443 if (r_txt_printer != nullptr) {
444 r_txt_printer->Println(
445 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700446 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800447 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700448
Ryan Mitchell5855de72021-02-24 14:39:13 -0800449 return true;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800450}
451
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800452void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
453 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700454 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800455 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700456 ResourceId real_id = id;
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000457 if (context_->GetMinSdkVersion() < SDK_O && name.type.type == ResourceType::kId &&
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700458 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800459 // Workaround for feature splits using package IDs > 0x7F.
460 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700461 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
462 }
463
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800464 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000465 if (out_class_def != nullptr) {
Ryan Mitchell2e9bec12021-03-22 09:31:00 -0700466 auto resource_member =
467 util::make_unique<ResourceMember>(field_name, real_id, entry.visibility.staged_api);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800468
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000469 // Build the comments and annotations for this entry.
470 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800471
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000472 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800473 if (entry.visibility.level != Visibility::Level::kUndefined) {
474 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800475 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800476
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000477 // Add the comments from all configurations of this entry.
478 for (const auto& config_value : entry.values) {
479 processor->AppendComment(config_value->value->GetComment());
480 }
481
482 // If this is an Attribute, append the format Javadoc.
483 if (!entry.values.empty()) {
484 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
485 // We list out the available values for the given attribute.
486 AddAttributeFormatDoc(processor, attr);
487 }
488 }
489
490 out_class_def->AddMember(std::move(resource_member));
491 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800492
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800493 if (r_txt_printer != nullptr) {
494 r_txt_printer->Print("int ")
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000495 .Print(name.type.to_string())
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800496 .Print(" ")
497 .Print(field_name)
498 .Print(" ")
499 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700500 }
501
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800502 if (out_rewrite_method != nullptr) {
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700503 const auto type_str = name.type.to_string();
Donald Chaid520db52019-11-25 23:05:51 -0800504 out_rewrite_method->AppendStatement(
505 StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
506 field_name.data(), type_str.data(), field_name.data()));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800507 }
508}
509
Ryan Mitchell4382e442021-07-14 12:53:01 -0700510std::optional<std::string> JavaClassGenerator::UnmangleResource(
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700511 StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800512 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800513 return {};
514 }
515
516 std::string unmangled_package;
517 std::string unmangled_name = entry.name;
518 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
519 // The entry name was mangled, and we successfully unmangled it.
520 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700521 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800522 // Skip the entry if it doesn't belong to the package we're writing.
523 return {};
524 }
525 } else if (package_name_to_generate != package_name) {
526 // We are processing a mangled package name,
527 // but this is a non-mangled resource.
528 return {};
529 }
530 return {std::move(unmangled_name)};
531}
532
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700533bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800534 const ResourceTablePackage& package,
535 const ResourceTableType& type,
536 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700537 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800538 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800539 for (const auto& entry : type.entries) {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700540 const std::optional<std::string> unmangled_name =
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800541 UnmangleResource(package.name, package_name_to_generate, *entry);
542 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700543 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800544 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700545
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800546 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700547 ResourceId id;
Ryan Mitchell9634efb2021-03-19 14:53:17 -0700548 if (entry->id) {
549 id = entry->id.value();
Adam Lesinski74605cd2016-03-03 15:39:50 -0800550 }
551
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800552 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000553 const auto target_type = type.named_type.type == ResourceType::kAttrPrivate
554 ? ResourceNamedTypeWithDefaultName(ResourceType::kAttr)
555 : type.named_type;
556 const ResourceNameRef resource_name(package_name_to_generate, target_type,
557 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800558
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800559 // Check to see if the unmangled name is a valid Java name (not a keyword).
560 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700561 std::stringstream err;
562 err << "invalid symbol name '" << resource_name << "'";
563 error_ = err.str();
564 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700565 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000567 if (resource_name.type.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700568 CHECK(!entry->values.empty());
Ryan Mitchell5855de72021-02-24 14:39:13 -0800569 const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get());
570 if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate,
571 out_type_class_def, out_rewrite_method_def, r_txt_printer)) {
572 return false;
573 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700574 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700575 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800576 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700577 }
578 }
579 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700580}
581
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700582bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800583 OutputStream* out_r_txt) {
584 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700585}
586
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800587static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
588 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700589 for (const std::string& annotation : annotations) {
590 std::string proper_annotation = "@";
591 proper_annotation += annotation;
592 processor->AppendComment(proper_annotation);
593 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700594}
595
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700596bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
597 StringPiece out_package_name, OutputStream* out,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800598 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800599 ClassDefinition r_class("R", ClassQualifier::kNone, true);
600 std::unique_ptr<MethodDefinition> rewrite_method;
601
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800602 std::unique_ptr<Printer> r_txt_printer;
603 if (out_r_txt != nullptr) {
604 r_txt_printer = util::make_unique<Printer>(out_r_txt);
605 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800606 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000607 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800608 rewrite_method =
609 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800610 for (const std::string& package_to_callback :
611 options_.rewrite_callback_options.value().packages_to_callback) {
612 rewrite_method->AppendStatement(
613 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
614 }
Donald Chaid520db52019-11-25 23:05:51 -0800615 rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800616 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700617
Makoto Onukide6e6f22020-06-22 10:17:02 -0700618 const bool is_public = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
619
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700620 for (const auto& package : table_->packages) {
621 for (const auto& type : package->types) {
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000622 if (type->named_type.type == ResourceType::kAttrPrivate ||
623 type->named_type.type == ResourceType::kMacro) {
Ryan Mitchell326e35ff2021-04-12 07:50:42 -0700624 // We generate kAttrPrivate as part of the kAttr type, so skip them here.
625 // Macros are not actual resources, so skip them as well.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700626 continue;
627 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800628
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700629 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Makoto Onukide6e6f22020-06-22 10:17:02 -0700630 const bool force_creation_if_empty = is_public;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800631
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000632 std::unique_ptr<ClassDefinition> class_def;
633 if (out != nullptr) {
634 class_def = util::make_unique<ClassDefinition>(
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000635 to_string(type->named_type.type), ClassQualifier::kStatic, force_creation_if_empty);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000636 }
637
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800638 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800639 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700640 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700642
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000643 if (type->named_type.type == ResourceType::kAttr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700644 // Also include private attributes in this same class.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000645 if (const ResourceTableType* priv_type =
646 package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800647 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800648 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700649 return false;
650 }
651 }
652 }
653
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000654 if (out != nullptr && type->named_type.type == ResourceType::kStyleable && is_public) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700655 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800656 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700657 class_def->GetCommentBuilder()->AppendComment("@doconly");
658 }
659
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000660 if (out != nullptr) {
661 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
662 r_class.AddMember(std::move(class_def));
663 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700664 }
665 }
666
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800667 if (rewrite_method != nullptr) {
668 r_class.AddMember(std::move(rewrite_method));
669 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700670
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000671 if (out != nullptr) {
672 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
Makoto Onukide6e6f22020-06-22 10:17:02 -0700673 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, !is_public, out);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000674 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700675 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800676}
677
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700678} // namespace aapt