blob: f0f839d968d5293001e6891dca1cf315a71051be [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.
207bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) {
208 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;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800215 Maybe<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) {
219 const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
220 const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(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
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800227void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
228 const Styleable& styleable,
229 const StringPiece& package_name_to_generate,
230 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700231 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800232 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800233 const std::string array_field_name = TransformToFieldName(name.entry);
234 std::unique_ptr<ResourceArrayMember> array_def =
235 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700236
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800237 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700238 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800239 sorted_attributes.reserve(styleable.entries.size());
240 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700241 // If we are not encoding final attributes, the styleable entry may have no
242 // ID if we are building a static library.
243 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
244 CHECK(bool(attr.name)) << "no name set for Styleable entry";
245
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800246 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700247 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800248 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700250
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800251 // The field name for this attribute is prefixed by the name of this styleable and
252 // the package it comes from.
253 styleable_attr.field_name =
254 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700255
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700256 Reference ref = attr;
257 if (attr.name.value().package.empty()) {
258
259 // If the resource does not have a package name, set the package to the unmangled package name
260 // of the styleable declaration because attributes without package names would have been
261 // declared in the same package as the styleable.
262 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
263 ref.name.value().entry);
264 }
265
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800266 // Look up the symbol so that we can write out in the comments what are possible legal values
267 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700268 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
269
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700270 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800271 // Copy the symbol data structure because the returned instance can be destroyed.
272 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 }
274 sorted_attributes.push_back(std::move(styleable_attr));
275 }
276
277 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800278 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700279
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800280 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
281 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700282 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000283 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700284 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800285 if (!styleable.GetComment().empty()) {
286 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800287 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800288 // Apply a default intro comment if the styleable has no comments of its own.
289 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800290 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700291
292 styleable_comment << "<p>Includes the following attributes:</p>\n"
293 "<table>\n"
294 "<colgroup align=\"left\" />\n"
295 "<colgroup align=\"left\" />\n"
296 "<tr><th>Attribute</th><th>Description</th></tr>\n";
297
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800298 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
299 // them as part of the class documentation.
300 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
301 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
302 documentation_attrs.end(),
303 [&](StyleableAttr entry) -> bool {
Ryan Mitchell4e19c482019-12-06 12:20:21 -0800304 if (SkipSymbol(entry.symbol)) {
305 return true;
306 }
307 const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
308 return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800309 });
310 documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700311
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800312 // Build the table of attributes with their links and names.
313 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700314 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700315 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
316 << (!attr_name.package.empty() ? attr_name.package
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700317 : package_name_to_generate)
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700318 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700319
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800320 // Only use the comment up until the first '.'. This is to stay compatible with
321 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700322 // annotations like @hide which would affect this Styleable).
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800323 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700324 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
325 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326 }
327 styleable_comment << "</table>\n";
328
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800329 // Generate the @see lines for each attribute.
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800330 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700331 styleable_comment << "@see #" << entry.field_name << "\n";
332 }
333
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800334 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700335 }
336
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800337 if (r_txt_printer != nullptr) {
338 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700339 }
340
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700341 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700342 for (size_t i = 0; i < attr_count; i++) {
343 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800344 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700345
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800346 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700347 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800348 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700349 }
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800350 r_txt_printer->Print(" ").Print(id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700351 }
352 }
353
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800354 if (r_txt_printer != nullptr) {
355 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700356 }
357
358 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800359 if (out_class_def != nullptr) {
360 out_class_def->AddMember(std::move(array_def));
361 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700362
363 // Now we emit the indices into the array.
364 for (size_t i = 0; i < attr_count; i++) {
365 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800366 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700367 continue;
368 }
369
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000370 if (out_class_def != nullptr) {
371 StringPiece comment = styleable_attr.attr_ref->GetComment();
372 if (styleable_attr.symbol.value().attribute && comment.empty()) {
373 comment = styleable_attr.symbol.value().attribute->GetComment();
374 }
375
376 if (comment.contains("@removed")) {
377 // Removed attributes are public but hidden from the documentation, so
378 // don't emit them as part of the class documentation.
379 continue;
380 }
381
382 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
383
384 StringPiece package_name = attr_name.package;
385 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700386 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000387 }
388
389 std::unique_ptr<IntMember> index_member =
390 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
391
392 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
393
394 if (!comment.empty()) {
395 attr_processor->AppendComment("<p>\n@attr description");
396 attr_processor->AppendComment(comment);
397 } else {
398 std::stringstream default_comment;
399 default_comment << "<p>This symbol is the offset where the "
400 << "{@link " << package_name << ".R.attr#"
401 << TransformToFieldName(attr_name.entry) << "}\n"
402 << "attribute's value can be found in the "
403 << "{@link #" << array_field_name << "} array.";
404 attr_processor->AppendComment(default_comment.str());
405 }
406
407 attr_processor->AppendNewLine();
408 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
409 attr_processor->AppendNewLine();
410 attr_processor->AppendComment(
411 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
412
413 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700414 }
415
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800416 if (r_txt_printer != nullptr) {
417 r_txt_printer->Println(
418 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700419 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800420 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700421
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800422 // If there is a rewrite method to generate, add the statements that rewrite package IDs
423 // for this styleable.
424 if (out_rewrite_method != nullptr) {
425 out_rewrite_method->AppendStatement(
426 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
427 out_rewrite_method->AppendStatement(
428 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
429 out_rewrite_method->AppendStatement(
Donald Chaid520db52019-11-25 23:05:51 -0800430 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;",
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800431 array_field_name.data(), array_field_name.data()));
432 out_rewrite_method->AppendStatement(" }");
433 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700434 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800435}
436
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800437void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
438 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700439 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800440 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700441 ResourceId real_id = id;
442 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
443 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800444 // Workaround for feature splits using package IDs > 0x7F.
445 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700446 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
447 }
448
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800449 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000450 if (out_class_def != nullptr) {
451 std::unique_ptr<ResourceMember> resource_member =
452 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800453
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000454 // Build the comments and annotations for this entry.
455 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800456
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000457 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800458 if (entry.visibility.level != Visibility::Level::kUndefined) {
459 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800460 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800461
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000462 // Add the comments from all configurations of this entry.
463 for (const auto& config_value : entry.values) {
464 processor->AppendComment(config_value->value->GetComment());
465 }
466
467 // If this is an Attribute, append the format Javadoc.
468 if (!entry.values.empty()) {
469 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
470 // We list out the available values for the given attribute.
471 AddAttributeFormatDoc(processor, attr);
472 }
473 }
474
475 out_class_def->AddMember(std::move(resource_member));
476 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800477
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800478 if (r_txt_printer != nullptr) {
479 r_txt_printer->Print("int ")
480 .Print(to_string(name.type))
481 .Print(" ")
482 .Print(field_name)
483 .Print(" ")
484 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700485 }
486
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800487 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700488 const StringPiece& type_str = to_string(name.type);
Donald Chaid520db52019-11-25 23:05:51 -0800489 out_rewrite_method->AppendStatement(
490 StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
491 field_name.data(), type_str.data(), field_name.data()));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800492 }
493}
494
495Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
496 const StringPiece& package_name_to_generate,
497 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800498 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800499 return {};
500 }
501
502 std::string unmangled_package;
503 std::string unmangled_name = entry.name;
504 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
505 // The entry name was mangled, and we successfully unmangled it.
506 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700507 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800508 // Skip the entry if it doesn't belong to the package we're writing.
509 return {};
510 }
511 } else if (package_name_to_generate != package_name) {
512 // We are processing a mangled package name,
513 // but this is a non-mangled resource.
514 return {};
515 }
516 return {std::move(unmangled_name)};
517}
518
519bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
520 const ResourceTablePackage& package,
521 const ResourceTableType& type,
522 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700523 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800524 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800525 for (const auto& entry : type.entries) {
526 const Maybe<std::string> unmangled_name =
527 UnmangleResource(package.name, package_name_to_generate, *entry);
528 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700529 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800530 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700531
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800532 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700533 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800534 if (package.id && type.id && entry->id) {
535 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800536 }
537
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800538 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
539 const ResourceNameRef resource_name(
540 package_name_to_generate,
541 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
542 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800543
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800544 // Check to see if the unmangled name is a valid Java name (not a keyword).
545 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700546 std::stringstream err;
547 err << "invalid symbol name '" << resource_name << "'";
548 error_ = err.str();
549 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700550 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700551
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800552 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700553 CHECK(!entry->values.empty());
554
555 const Styleable* styleable =
556 static_cast<const Styleable*>(entry->values.front()->value.get());
557
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800558 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800559 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700560 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700561 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800562 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700563 }
564 }
565 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700566}
567
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800568bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
569 OutputStream* out_r_txt) {
570 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700571}
572
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800573static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
574 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700575 for (const std::string& annotation : annotations) {
576 std::string proper_annotation = "@";
577 proper_annotation += annotation;
578 processor->AppendComment(proper_annotation);
579 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700580}
581
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700582bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800583 const StringPiece& out_package_name, OutputStream* out,
584 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800585 ClassDefinition r_class("R", ClassQualifier::kNone, true);
586 std::unique_ptr<MethodDefinition> rewrite_method;
587
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800588 std::unique_ptr<Printer> r_txt_printer;
589 if (out_r_txt != nullptr) {
590 r_txt_printer = util::make_unique<Printer>(out_r_txt);
591 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800592 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000593 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800594 rewrite_method =
595 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800596 for (const std::string& package_to_callback :
597 options_.rewrite_callback_options.value().packages_to_callback) {
598 rewrite_method->AppendStatement(
599 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
600 }
Donald Chaid520db52019-11-25 23:05:51 -0800601 rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800602 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700603
Makoto Onukide6e6f22020-06-22 10:17:02 -0700604 const bool is_public = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
605
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700606 for (const auto& package : table_->packages) {
607 for (const auto& type : package->types) {
608 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800609 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700610 continue;
611 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800612
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700613 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Makoto Onukide6e6f22020-06-22 10:17:02 -0700614 const bool force_creation_if_empty = is_public;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800615
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000616 std::unique_ptr<ClassDefinition> class_def;
617 if (out != nullptr) {
618 class_def = util::make_unique<ClassDefinition>(
619 to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
620 }
621
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800622 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800623 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700624 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700625 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700626
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700627 if (type->type == ResourceType::kAttr) {
628 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800629 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700630 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800631 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800632 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700633 return false;
634 }
635 }
636 }
637
Makoto Onukide6e6f22020-06-22 10:17:02 -0700638 if (out != nullptr && type->type == ResourceType::kStyleable && is_public) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700639 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800640 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 class_def->GetCommentBuilder()->AppendComment("@doconly");
642 }
643
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000644 if (out != nullptr) {
645 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
646 r_class.AddMember(std::move(class_def));
647 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700648 }
649 }
650
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800651 if (rewrite_method != nullptr) {
652 r_class.AddMember(std::move(rewrite_method));
653 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700654
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000655 if (out != nullptr) {
656 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
Makoto Onukide6e6f22020-06-22 10:17:02 -0700657 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, !is_public, out);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000658 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700659 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800660}
661
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700662} // namespace aapt