blob: a25ca22c288da3c547d1e35073c92143f84ba9c9 [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
Adam Lesinskice5e56e2016-10-21 17:56:45 -070060static bool IsValidSymbol(const StringPiece& symbol) {
61 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 '_'.
Adam Koskidc21dea2017-07-21 10:55:27 -070066std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
Adam Lesinskid5083f62017-01-16 15:07:21 -080067 std::string output = symbol.to_string();
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,
87 const 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) {
229 const std::string package_name =
230 name.package.empty() ? fallback_package_name.to_string() : name.package;
231 const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000232 return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(),
233 name.type.to_string().data(), entry.c_str()));
Ryan Mitchell5855de72021-02-24 14:39:13 -0800234}
235
236bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800237 const Styleable& styleable,
238 const StringPiece& package_name_to_generate,
239 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700240 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800241 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800242 const std::string array_field_name = TransformToFieldName(name.entry);
243 std::unique_ptr<ResourceArrayMember> array_def =
244 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700245
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800246 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700247 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800248 sorted_attributes.reserve(styleable.entries.size());
249 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700250 // If we are not encoding final attributes, the styleable entry may have no
251 // ID if we are building a static library.
252 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
253 CHECK(bool(attr.name)) << "no name set for Styleable entry";
254
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800255 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700256 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800257 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700259
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800260 // The field name for this attribute is prefixed by the name of this styleable and
261 // the package it comes from.
262 styleable_attr.field_name =
263 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700264
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700265 Reference ref = attr;
266 if (attr.name.value().package.empty()) {
267
268 // If the resource does not have a package name, set the package to the unmangled package name
269 // of the styleable declaration because attributes without package names would have been
270 // declared in the same package as the styleable.
271 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
272 ref.name.value().entry);
273 }
274
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800275 // Look up the symbol so that we can write out in the comments what are possible legal values
276 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700277 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
278
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700279 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800280 // Copy the symbol data structure because the returned instance can be destroyed.
281 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700282 }
283 sorted_attributes.push_back(std::move(styleable_attr));
284 }
285
286 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800287 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700288
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800289 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
290 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700291 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000292 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700293 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800294 if (!styleable.GetComment().empty()) {
295 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800296 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800297 // Apply a default intro comment if the styleable has no comments of its own.
298 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800299 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700300
301 styleable_comment << "<p>Includes the following attributes:</p>\n"
302 "<table>\n"
303 "<colgroup align=\"left\" />\n"
304 "<colgroup align=\"left\" />\n"
305 "<tr><th>Attribute</th><th>Description</th></tr>\n";
306
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800307 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
308 // them as part of the class documentation.
309 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
310 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
311 documentation_attrs.end(),
312 [&](StyleableAttr entry) -> bool {
Ryan Mitchell4e19c482019-12-06 12:20:21 -0800313 if (SkipSymbol(entry.symbol)) {
314 return true;
315 }
316 const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
317 return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
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
400 if (comment.contains("@removed")) {
401 // 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) {
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000500 const std::string 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(
508 const StringPiece& package_name, const StringPiece& package_name_to_generate,
509 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800510 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800511 return {};
512 }
513
514 std::string unmangled_package;
515 std::string unmangled_name = entry.name;
516 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
517 // The entry name was mangled, and we successfully unmangled it.
518 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700519 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800520 // Skip the entry if it doesn't belong to the package we're writing.
521 return {};
522 }
523 } else if (package_name_to_generate != package_name) {
524 // We are processing a mangled package name,
525 // but this is a non-mangled resource.
526 return {};
527 }
528 return {std::move(unmangled_name)};
529}
530
531bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
532 const ResourceTablePackage& package,
533 const ResourceTableType& type,
534 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700535 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800536 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800537 for (const auto& entry : type.entries) {
Ryan Mitchell4382e442021-07-14 12:53:01 -0700538 const std::optional<std::string> unmangled_name =
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800539 UnmangleResource(package.name, package_name_to_generate, *entry);
540 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700541 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800542 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700543
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800544 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700545 ResourceId id;
Ryan Mitchell9634efb2021-03-19 14:53:17 -0700546 if (entry->id) {
547 id = entry->id.value();
Adam Lesinski74605cd2016-03-03 15:39:50 -0800548 }
549
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800550 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000551 const auto target_type = type.named_type.type == ResourceType::kAttrPrivate
552 ? ResourceNamedTypeWithDefaultName(ResourceType::kAttr)
553 : type.named_type;
554 const ResourceNameRef resource_name(package_name_to_generate, target_type,
555 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800556
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800557 // Check to see if the unmangled name is a valid Java name (not a keyword).
558 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700559 std::stringstream err;
560 err << "invalid symbol name '" << resource_name << "'";
561 error_ = err.str();
562 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700563 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700564
Iurii Makhnocff10ce2022-02-15 19:33:50 +0000565 if (resource_name.type.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566 CHECK(!entry->values.empty());
Ryan Mitchell5855de72021-02-24 14:39:13 -0800567 const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get());
568 if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate,
569 out_type_class_def, out_rewrite_method_def, r_txt_printer)) {
570 return false;
571 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700572 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700573 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800574 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700575 }
576 }
577 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700578}
579
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800580bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
581 OutputStream* out_r_txt) {
582 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700583}
584
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800585static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
586 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700587 for (const std::string& annotation : annotations) {
588 std::string proper_annotation = "@";
589 proper_annotation += annotation;
590 processor->AppendComment(proper_annotation);
591 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700592}
593
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700594bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800595 const StringPiece& out_package_name, OutputStream* out,
596 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800597 ClassDefinition r_class("R", ClassQualifier::kNone, true);
598 std::unique_ptr<MethodDefinition> rewrite_method;
599
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800600 std::unique_ptr<Printer> r_txt_printer;
601 if (out_r_txt != nullptr) {
602 r_txt_printer = util::make_unique<Printer>(out_r_txt);
603 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800604 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000605 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800606 rewrite_method =
607 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800608 for (const std::string& package_to_callback :
609 options_.rewrite_callback_options.value().packages_to_callback) {
610 rewrite_method->AppendStatement(
611 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
612 }
Donald Chaid520db52019-11-25 23:05:51 -0800613 rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800614 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700615
Makoto Onukide6e6f22020-06-22 10:17:02 -0700616 const bool is_public = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
617
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700618 for (const auto& package : table_->packages) {
619 for (const auto& type : package->types) {
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000620 if (type->named_type.type == ResourceType::kAttrPrivate ||
621 type->named_type.type == ResourceType::kMacro) {
Ryan Mitchell326e35ff2021-04-12 07:50:42 -0700622 // We generate kAttrPrivate as part of the kAttr type, so skip them here.
623 // Macros are not actual resources, so skip them as well.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700624 continue;
625 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800626
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700627 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Makoto Onukide6e6f22020-06-22 10:17:02 -0700628 const bool force_creation_if_empty = is_public;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800629
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000630 std::unique_ptr<ClassDefinition> class_def;
631 if (out != nullptr) {
632 class_def = util::make_unique<ClassDefinition>(
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000633 to_string(type->named_type.type), ClassQualifier::kStatic, force_creation_if_empty);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000634 }
635
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800636 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800637 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700638 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700639 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700640
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000641 if (type->named_type.type == ResourceType::kAttr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700642 // Also include private attributes in this same class.
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000643 if (const ResourceTableType* priv_type =
644 package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800645 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800646 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700647 return false;
648 }
649 }
650 }
651
Iurii Makhnof0c5ff42022-02-22 13:31:02 +0000652 if (out != nullptr && type->named_type.type == ResourceType::kStyleable && is_public) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700653 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800654 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700655 class_def->GetCommentBuilder()->AppendComment("@doconly");
656 }
657
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000658 if (out != nullptr) {
659 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
660 r_class.AddMember(std::move(class_def));
661 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700662 }
663 }
664
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800665 if (rewrite_method != nullptr) {
666 r_class.AddMember(std::move(rewrite_method));
667 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700668
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000669 if (out != nullptr) {
670 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
Makoto Onukide6e6f22020-06-22 10:17:02 -0700671 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, !is_public, out);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000672 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700673 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800674}
675
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700676} // namespace aapt