blob: 7665d0e8d9cb4a9e2d527f59f6fc846d202fee88 [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::io::OutputStream;
41using ::aapt::text::Printer;
42using ::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>";
183 processor->AppendComment(line.str());
Adam Lesinski76565542016-03-10 21:55:04 -0800184 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700185 processor->AppendComment("</table>");
186 }
Adam Lesinski76565542016-03-10 21:55:04 -0800187}
188
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700189JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
190 ResourceTable* table,
191 const JavaClassGeneratorOptions& options)
192 : context_(context), table_(table), options_(options) {}
193
Adam Lesinski71be7052017-12-12 16:48:07 -0800194bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700195 switch (options_.types) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700196 case JavaClassGeneratorOptions::SymbolTypes::kAll:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700197 return false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700198 case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
Adam Lesinski71be7052017-12-12 16:48:07 -0800199 return level == Visibility::Level::kUndefined;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700200 case JavaClassGeneratorOptions::SymbolTypes::kPublic:
Adam Lesinski71be7052017-12-12 16:48:07 -0800201 return level != Visibility::Level::kPublic;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700202 }
203 return true;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700204}
205
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800206// Whether or not to skip writing this symbol.
Ryan Mitchell4382e442021-07-14 12:53:01 -0700207bool JavaClassGenerator::SkipSymbol(const std::optional<SymbolTable::Symbol>& symbol) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800208 return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
209 !symbol.value().is_public);
210}
211
Adam Lesinski74605cd2016-03-03 15:39:50 -0800212struct StyleableAttr {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800213 const Reference* attr_ref = nullptr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700214 std::string field_name;
Ryan Mitchell4382e442021-07-14 12:53:01 -0700215 std::optional<SymbolTable::Symbol> symbol;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800216};
217
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800218static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700219 const ResourceId lhs_id = lhs.attr_ref->id.value_or(ResourceId(0));
220 const ResourceId rhs_id = rhs.attr_ref->id.value_or(ResourceId(0));
Clark DuVall8f51d6b2020-04-22 13:19:28 -0700221 if (lhs_id == rhs_id) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700222 return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
223 }
Clark DuVall8f51d6b2020-04-22 13:19:28 -0700224 return cmp_ids_dynamic_after_framework(lhs_id, rhs_id);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700225}
226
Ryan Mitchell5855de72021-02-24 14:39:13 -0800227static FieldReference GetRFieldReference(const ResourceName& name,
228 StringPiece fallback_package_name) {
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700229 const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
Ryan Mitchell5855de72021-02-24 14:39:13 -0800230 const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700231 return FieldReference(
232 StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
Ryan Mitchell5855de72021-02-24 14:39:13 -0800233}
234
235bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800236 const Styleable& styleable,
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700237 StringPiece package_name_to_generate,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800238 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700239 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800240 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800241 const std::string array_field_name = TransformToFieldName(name.entry);
242 std::unique_ptr<ResourceArrayMember> array_def =
243 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700244
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800245 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700246 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800247 sorted_attributes.reserve(styleable.entries.size());
248 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249 // If we are not encoding final attributes, the styleable entry may have no
250 // ID if we are building a static library.
251 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
252 CHECK(bool(attr.name)) << "no name set for Styleable entry";
253
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800254 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700255 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800256 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700257 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800259 // The field name for this attribute is prefixed by the name of this styleable and
260 // the package it comes from.
261 styleable_attr.field_name =
262 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700263
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700264 Reference ref = attr;
265 if (attr.name.value().package.empty()) {
266
267 // If the resource does not have a package name, set the package to the unmangled package name
268 // of the styleable declaration because attributes without package names would have been
269 // declared in the same package as the styleable.
270 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
271 ref.name.value().entry);
272 }
273
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800274 // Look up the symbol so that we can write out in the comments what are possible legal values
275 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700276 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
277
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700278 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800279 // Copy the symbol data structure because the returned instance can be destroyed.
280 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700281 }
282 sorted_attributes.push_back(std::move(styleable_attr));
283 }
284
285 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800286 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700287
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800288 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
289 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700290 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000291 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700292 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800293 if (!styleable.GetComment().empty()) {
294 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800295 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800296 // Apply a default intro comment if the styleable has no comments of its own.
297 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800298 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700299
300 styleable_comment << "<p>Includes the following attributes:</p>\n"
301 "<table>\n"
302 "<colgroup align=\"left\" />\n"
303 "<colgroup align=\"left\" />\n"
304 "<tr><th>Attribute</th><th>Description</th></tr>\n";
305
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800306 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
307 // them as part of the class documentation.
308 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
309 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
310 documentation_attrs.end(),
311 [&](StyleableAttr entry) -> bool {
Ryan Mitchell4e19c482019-12-06 12:20:21 -0800312 if (SkipSymbol(entry.symbol)) {
313 return true;
314 }
315 const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700316 return attr_comment_line.find("@removed") != std::string::npos ||
317 attr_comment_line.find("@hide") != std::string::npos;
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800318 });
319 documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700320
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800321 // Build the table of attributes with their links and names.
322 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700324 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
325 << (!attr_name.package.empty() ? attr_name.package
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700326 : package_name_to_generate)
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700327 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700328
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800329 // Only use the comment up until the first '.'. This is to stay compatible with
330 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700331 // annotations like @hide which would affect this Styleable).
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800332 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700333 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
334 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700335 }
336 styleable_comment << "</table>\n";
337
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800338 // Generate the @see lines for each attribute.
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800339 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700340 styleable_comment << "@see #" << entry.field_name << "\n";
341 }
342
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800343 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700344 }
345
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800346 if (r_txt_printer != nullptr) {
347 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700348 }
349
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700350 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700351 for (size_t i = 0; i < attr_count; i++) {
Ryan Mitchell5855de72021-02-24 14:39:13 -0800352 const StyleableAttr& attr = sorted_attributes[i];
353 std::string r_txt_contents;
354 if (attr.symbol && attr.symbol.value().is_dynamic) {
355 if (!attr.attr_ref->name) {
356 error_ = "unable to determine R.java field name of dynamic resource";
357 return false;
358 }
359
360 const FieldReference field_name =
361 GetRFieldReference(attr.attr_ref->name.value(), package_name_to_generate);
362 array_def->AddElement(field_name);
363 r_txt_contents = field_name.ref;
364 } else {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700365 const ResourceId attr_id = attr.attr_ref->id.value_or(ResourceId(0));
Ryan Mitchell5855de72021-02-24 14:39:13 -0800366 array_def->AddElement(attr_id);
367 r_txt_contents = to_string(attr_id);
368 }
Adam Lesinski418763f2017-04-11 17:36:53 -0700369
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800370 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700371 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800372 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700373 }
Ryan Mitchell5855de72021-02-24 14:39:13 -0800374 r_txt_printer->Print(" ").Print(r_txt_contents);
Adam Lesinski418763f2017-04-11 17:36:53 -0700375 }
376 }
377
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800378 if (r_txt_printer != nullptr) {
379 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700380 }
381
382 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800383 if (out_class_def != nullptr) {
384 out_class_def->AddMember(std::move(array_def));
385 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700386
387 // Now we emit the indices into the array.
388 for (size_t i = 0; i < attr_count; i++) {
389 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800390 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700391 continue;
392 }
393
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000394 if (out_class_def != nullptr) {
395 StringPiece comment = styleable_attr.attr_ref->GetComment();
396 if (styleable_attr.symbol.value().attribute && comment.empty()) {
397 comment = styleable_attr.symbol.value().attribute->GetComment();
398 }
399
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700400 if (comment.find("@removed") != std::string::npos) {
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000401 // Removed attributes are public but hidden from the documentation, so
402 // don't emit them as part of the class documentation.
403 continue;
404 }
405
406 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
407
408 StringPiece package_name = attr_name.package;
409 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700410 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000411 }
412
413 std::unique_ptr<IntMember> index_member =
414 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
415
416 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
417
418 if (!comment.empty()) {
419 attr_processor->AppendComment("<p>\n@attr description");
420 attr_processor->AppendComment(comment);
421 } else {
422 std::stringstream default_comment;
423 default_comment << "<p>This symbol is the offset where the "
424 << "{@link " << package_name << ".R.attr#"
425 << TransformToFieldName(attr_name.entry) << "}\n"
426 << "attribute's value can be found in the "
427 << "{@link #" << array_field_name << "} array.";
428 attr_processor->AppendComment(default_comment.str());
429 }
430
431 attr_processor->AppendNewLine();
432 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
433 attr_processor->AppendNewLine();
434 attr_processor->AppendComment(
435 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
436
437 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700438 }
439
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800440 if (r_txt_printer != nullptr) {
441 r_txt_printer->Println(
442 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700443 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800444 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700445
Ryan Mitchell5855de72021-02-24 14:39:13 -0800446 return true;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800447}
448
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800449void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
450 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700451 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800452 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700453 ResourceId real_id = id;
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000454 if (context_->GetMinSdkVersion() < SDK_O && name.type.type == ResourceType::kId &&
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700455 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800456 // Workaround for feature splits using package IDs > 0x7F.
457 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700458 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
459 }
460
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800461 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000462 if (out_class_def != nullptr) {
Ryan Mitchell2e9bec12021-03-22 09:31:00 -0700463 auto resource_member =
464 util::make_unique<ResourceMember>(field_name, real_id, entry.visibility.staged_api);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800465
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000466 // Build the comments and annotations for this entry.
467 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800468
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000469 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800470 if (entry.visibility.level != Visibility::Level::kUndefined) {
471 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800472 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800473
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000474 // Add the comments from all configurations of this entry.
475 for (const auto& config_value : entry.values) {
476 processor->AppendComment(config_value->value->GetComment());
477 }
478
479 // If this is an Attribute, append the format Javadoc.
480 if (!entry.values.empty()) {
481 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
482 // We list out the available values for the given attribute.
483 AddAttributeFormatDoc(processor, attr);
484 }
485 }
486
487 out_class_def->AddMember(std::move(resource_member));
488 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800489
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800490 if (r_txt_printer != nullptr) {
491 r_txt_printer->Print("int ")
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000492 .Print(name.type.to_string())
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800493 .Print(" ")
494 .Print(field_name)
495 .Print(" ")
496 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700497 }
498
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800499 if (out_rewrite_method != nullptr) {
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700500 const auto type_str = name.type.to_string();
Donald Chaid520db52019-11-25 23:05:51 -0800501 out_rewrite_method->AppendStatement(
502 StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
503 field_name.data(), type_str.data(), field_name.data()));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800504 }
505}
506
Ryan Mitchell4382e442021-07-14 12:53:01 -0700507std::optional<std::string> JavaClassGenerator::UnmangleResource(
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700508 StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800509 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800510 return {};
511 }
512
513 std::string unmangled_package;
514 std::string unmangled_name = entry.name;
515 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
516 // The entry name was mangled, and we successfully unmangled it.
517 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700518 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800519 // Skip the entry if it doesn't belong to the package we're writing.
520 return {};
521 }
522 } else if (package_name_to_generate != package_name) {
523 // We are processing a mangled package name,
524 // but this is a non-mangled resource.
525 return {};
526 }
527 return {std::move(unmangled_name)};
528}
529
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700530bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800531 const ResourceTablePackage& package,
532 const ResourceTableType& type,
533 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700534 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800535 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800536 for (const auto& entry : type.entries) {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700537 const std::optional<std::string> unmangled_name =
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800538 UnmangleResource(package.name, package_name_to_generate, *entry);
539 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800541 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700542
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800543 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700544 ResourceId id;
Ryan Mitchell9634efb2021-03-19 14:53:17 -0700545 if (entry->id) {
546 id = entry->id.value();
Adam Lesinski74605cd2016-03-03 15:39:50 -0800547 }
548
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800549 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000550 const auto target_type = type.named_type.type == ResourceType::kAttrPrivate
551 ? ResourceNamedTypeWithDefaultName(ResourceType::kAttr)
552 : type.named_type;
553 const ResourceNameRef resource_name(package_name_to_generate, target_type,
554 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800555
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800556 // Check to see if the unmangled name is a valid Java name (not a keyword).
557 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700558 std::stringstream err;
559 err << "invalid symbol name '" << resource_name << "'";
560 error_ = err.str();
561 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700562 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700563
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000564 if (resource_name.type.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700565 CHECK(!entry->values.empty());
Ryan Mitchell5855de72021-02-24 14:39:13 -0800566 const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get());
567 if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate,
568 out_type_class_def, out_rewrite_method_def, r_txt_printer)) {
569 return false;
570 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700571 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700572 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800573 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700574 }
575 }
576 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700577}
578
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700579bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800580 OutputStream* out_r_txt) {
581 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700582}
583
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800584static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
585 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700586 for (const std::string& annotation : annotations) {
587 std::string proper_annotation = "@";
588 proper_annotation += annotation;
589 processor->AppendComment(proper_annotation);
590 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700591}
592
Yurii Zubrytskyia5775142022-11-02 17:49:49 -0700593bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
594 StringPiece out_package_name, OutputStream* out,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800595 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800596 ClassDefinition r_class("R", ClassQualifier::kNone, true);
597 std::unique_ptr<MethodDefinition> rewrite_method;
598
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800599 std::unique_ptr<Printer> r_txt_printer;
600 if (out_r_txt != nullptr) {
601 r_txt_printer = util::make_unique<Printer>(out_r_txt);
602 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800603 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000604 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800605 rewrite_method =
606 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800607 for (const std::string& package_to_callback :
608 options_.rewrite_callback_options.value().packages_to_callback) {
609 rewrite_method->AppendStatement(
610 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
611 }
Donald Chaid520db52019-11-25 23:05:51 -0800612 rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800613 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700614
Makoto Onukide6e6f22020-06-22 10:17:02 -0700615 const bool is_public = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
616
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700617 for (const auto& package : table_->packages) {
618 for (const auto& type : package->types) {
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000619 if (type->named_type.type == ResourceType::kAttrPrivate ||
620 type->named_type.type == ResourceType::kMacro) {
Ryan Mitchell326e35ff2021-04-12 07:50:42 -0700621 // We generate kAttrPrivate as part of the kAttr type, so skip them here.
622 // Macros are not actual resources, so skip them as well.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700623 continue;
624 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800625
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700626 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Makoto Onukide6e6f22020-06-22 10:17:02 -0700627 const bool force_creation_if_empty = is_public;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800628
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000629 std::unique_ptr<ClassDefinition> class_def;
630 if (out != nullptr) {
631 class_def = util::make_unique<ClassDefinition>(
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000632 to_string(type->named_type.type), ClassQualifier::kStatic, force_creation_if_empty);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000633 }
634
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800635 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800636 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700637 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700638 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700639
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000640 if (type->named_type.type == ResourceType::kAttr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 // Also include private attributes in this same class.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000642 if (const ResourceTableType* priv_type =
643 package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800644 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800645 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700646 return false;
647 }
648 }
649 }
650
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000651 if (out != nullptr && type->named_type.type == ResourceType::kStyleable && is_public) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700652 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800653 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700654 class_def->GetCommentBuilder()->AppendComment("@doconly");
655 }
656
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000657 if (out != nullptr) {
658 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
659 r_class.AddMember(std::move(class_def));
660 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700661 }
662 }
663
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800664 if (rewrite_method != nullptr) {
665 r_class.AddMember(std::move(rewrite_method));
666 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700667
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000668 if (out != nullptr) {
669 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
Makoto Onukide6e6f22020-06-22 10:17:02 -0700670 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, !is_public, out);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000671 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700672 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800673}
674
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700675} // namespace aapt