blob: bb541fe2490b311674a3e9311319aa764d4400e1 [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));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700221 if (lhs_id < rhs_id) {
222 return true;
223 } else if (lhs_id > rhs_id) {
224 return false;
225 } else {
226 return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
227 }
228}
229
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800230void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
231 const Styleable& styleable,
232 const StringPiece& package_name_to_generate,
233 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700234 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800235 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800236 const std::string array_field_name = TransformToFieldName(name.entry);
237 std::unique_ptr<ResourceArrayMember> array_def =
238 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700239
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800240 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700241 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800242 sorted_attributes.reserve(styleable.entries.size());
243 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700244 // If we are not encoding final attributes, the styleable entry may have no
245 // ID if we are building a static library.
246 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
247 CHECK(bool(attr.name)) << "no name set for Styleable entry";
248
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800249 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700250 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800251 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700252 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700253
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800254 // The field name for this attribute is prefixed by the name of this styleable and
255 // the package it comes from.
256 styleable_attr.field_name =
257 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700259 Reference ref = attr;
260 if (attr.name.value().package.empty()) {
261
262 // If the resource does not have a package name, set the package to the unmangled package name
263 // of the styleable declaration because attributes without package names would have been
264 // declared in the same package as the styleable.
265 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
266 ref.name.value().entry);
267 }
268
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800269 // Look up the symbol so that we can write out in the comments what are possible legal values
270 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700271 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
272
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800274 // Copy the symbol data structure because the returned instance can be destroyed.
275 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 }
277 sorted_attributes.push_back(std::move(styleable_attr));
278 }
279
280 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800281 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700282
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800283 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
284 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700285 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000286 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700287 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800288 if (!styleable.GetComment().empty()) {
289 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800290 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800291 // Apply a default intro comment if the styleable has no comments of its own.
292 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800293 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700294
295 styleable_comment << "<p>Includes the following attributes:</p>\n"
296 "<table>\n"
297 "<colgroup align=\"left\" />\n"
298 "<colgroup align=\"left\" />\n"
299 "<tr><th>Attribute</th><th>Description</th></tr>\n";
300
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800301 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
302 // them as part of the class documentation.
303 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
304 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
305 documentation_attrs.end(),
306 [&](StyleableAttr entry) -> bool {
Ryan Mitchell4e19c482019-12-06 12:20:21 -0800307 if (SkipSymbol(entry.symbol)) {
308 return true;
309 }
310 const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
311 return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800312 });
313 documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700314
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800315 // Build the table of attributes with their links and names.
316 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700317 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700318 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
319 << (!attr_name.package.empty() ? attr_name.package
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700320 : package_name_to_generate)
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700321 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700322
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800323 // Only use the comment up until the first '.'. This is to stay compatible with
324 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700325 // annotations like @hide which would affect this Styleable).
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800326 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700327 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
328 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700329 }
330 styleable_comment << "</table>\n";
331
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800332 // Generate the @see lines for each attribute.
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800333 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700334 styleable_comment << "@see #" << entry.field_name << "\n";
335 }
336
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800337 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700338 }
339
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800340 if (r_txt_printer != nullptr) {
341 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700342 }
343
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700344 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700345 for (size_t i = 0; i < attr_count; i++) {
346 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800347 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700348
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800349 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700350 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800351 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700352 }
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800353 r_txt_printer->Print(" ").Print(id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700354 }
355 }
356
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800357 if (r_txt_printer != nullptr) {
358 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700359 }
360
361 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800362 if (out_class_def != nullptr) {
363 out_class_def->AddMember(std::move(array_def));
364 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700365
366 // Now we emit the indices into the array.
367 for (size_t i = 0; i < attr_count; i++) {
368 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800369 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700370 continue;
371 }
372
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000373 if (out_class_def != nullptr) {
374 StringPiece comment = styleable_attr.attr_ref->GetComment();
375 if (styleable_attr.symbol.value().attribute && comment.empty()) {
376 comment = styleable_attr.symbol.value().attribute->GetComment();
377 }
378
379 if (comment.contains("@removed")) {
380 // Removed attributes are public but hidden from the documentation, so
381 // don't emit them as part of the class documentation.
382 continue;
383 }
384
385 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
386
387 StringPiece package_name = attr_name.package;
388 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700389 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000390 }
391
392 std::unique_ptr<IntMember> index_member =
393 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
394
395 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
396
397 if (!comment.empty()) {
398 attr_processor->AppendComment("<p>\n@attr description");
399 attr_processor->AppendComment(comment);
400 } else {
401 std::stringstream default_comment;
402 default_comment << "<p>This symbol is the offset where the "
403 << "{@link " << package_name << ".R.attr#"
404 << TransformToFieldName(attr_name.entry) << "}\n"
405 << "attribute's value can be found in the "
406 << "{@link #" << array_field_name << "} array.";
407 attr_processor->AppendComment(default_comment.str());
408 }
409
410 attr_processor->AppendNewLine();
411 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
412 attr_processor->AppendNewLine();
413 attr_processor->AppendComment(
414 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
415
416 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700417 }
418
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800419 if (r_txt_printer != nullptr) {
420 r_txt_printer->Println(
421 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700422 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800423 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700424
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800425 // If there is a rewrite method to generate, add the statements that rewrite package IDs
426 // for this styleable.
427 if (out_rewrite_method != nullptr) {
428 out_rewrite_method->AppendStatement(
429 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
430 out_rewrite_method->AppendStatement(
431 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
432 out_rewrite_method->AppendStatement(
Donald Chaid520db52019-11-25 23:05:51 -0800433 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;",
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800434 array_field_name.data(), array_field_name.data()));
435 out_rewrite_method->AppendStatement(" }");
436 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700437 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800438}
439
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800440void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
441 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700442 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800443 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700444 ResourceId real_id = id;
445 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
446 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800447 // Workaround for feature splits using package IDs > 0x7F.
448 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700449 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
450 }
451
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800452 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000453 if (out_class_def != nullptr) {
454 std::unique_ptr<ResourceMember> resource_member =
455 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800456
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000457 // Build the comments and annotations for this entry.
458 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800459
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000460 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800461 if (entry.visibility.level != Visibility::Level::kUndefined) {
462 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800463 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800464
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000465 // Add the comments from all configurations of this entry.
466 for (const auto& config_value : entry.values) {
467 processor->AppendComment(config_value->value->GetComment());
468 }
469
470 // If this is an Attribute, append the format Javadoc.
471 if (!entry.values.empty()) {
472 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
473 // We list out the available values for the given attribute.
474 AddAttributeFormatDoc(processor, attr);
475 }
476 }
477
478 out_class_def->AddMember(std::move(resource_member));
479 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800480
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800481 if (r_txt_printer != nullptr) {
482 r_txt_printer->Print("int ")
483 .Print(to_string(name.type))
484 .Print(" ")
485 .Print(field_name)
486 .Print(" ")
487 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700488 }
489
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800490 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700491 const StringPiece& type_str = to_string(name.type);
Donald Chaid520db52019-11-25 23:05:51 -0800492 out_rewrite_method->AppendStatement(
493 StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
494 field_name.data(), type_str.data(), field_name.data()));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800495 }
496}
497
498Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
499 const StringPiece& package_name_to_generate,
500 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800501 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800502 return {};
503 }
504
505 std::string unmangled_package;
506 std::string unmangled_name = entry.name;
507 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
508 // The entry name was mangled, and we successfully unmangled it.
509 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700510 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800511 // Skip the entry if it doesn't belong to the package we're writing.
512 return {};
513 }
514 } else if (package_name_to_generate != package_name) {
515 // We are processing a mangled package name,
516 // but this is a non-mangled resource.
517 return {};
518 }
519 return {std::move(unmangled_name)};
520}
521
522bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
523 const ResourceTablePackage& package,
524 const ResourceTableType& type,
525 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700526 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800527 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800528 for (const auto& entry : type.entries) {
529 const Maybe<std::string> unmangled_name =
530 UnmangleResource(package.name, package_name_to_generate, *entry);
531 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700532 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800533 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700534
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800535 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700536 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800537 if (package.id && type.id && entry->id) {
538 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800539 }
540
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800541 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
542 const ResourceNameRef resource_name(
543 package_name_to_generate,
544 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
545 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800546
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800547 // Check to see if the unmangled name is a valid Java name (not a keyword).
548 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700549 std::stringstream err;
550 err << "invalid symbol name '" << resource_name << "'";
551 error_ = err.str();
552 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700553 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700554
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800555 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700556 CHECK(!entry->values.empty());
557
558 const Styleable* styleable =
559 static_cast<const Styleable*>(entry->values.front()->value.get());
560
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800561 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800562 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700563 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700564 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800565 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566 }
567 }
568 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700569}
570
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800571bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
572 OutputStream* out_r_txt) {
573 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700574}
575
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800576static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
577 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700578 for (const std::string& annotation : annotations) {
579 std::string proper_annotation = "@";
580 proper_annotation += annotation;
581 processor->AppendComment(proper_annotation);
582 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700583}
584
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700585bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800586 const StringPiece& out_package_name, OutputStream* out,
587 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800588 ClassDefinition r_class("R", ClassQualifier::kNone, true);
589 std::unique_ptr<MethodDefinition> rewrite_method;
590
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800591 std::unique_ptr<Printer> r_txt_printer;
592 if (out_r_txt != nullptr) {
593 r_txt_printer = util::make_unique<Printer>(out_r_txt);
594 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800595 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000596 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800597 rewrite_method =
598 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800599 for (const std::string& package_to_callback :
600 options_.rewrite_callback_options.value().packages_to_callback) {
601 rewrite_method->AppendStatement(
602 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
603 }
Donald Chaid520db52019-11-25 23:05:51 -0800604 rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800605 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700606
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700607 for (const auto& package : table_->packages) {
608 for (const auto& type : package->types) {
609 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800610 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700611 continue;
612 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800613
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700614 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700615 const bool force_creation_if_empty =
616 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800617
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000618 std::unique_ptr<ClassDefinition> class_def;
619 if (out != nullptr) {
620 class_def = util::make_unique<ClassDefinition>(
621 to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
622 }
623
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800624 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800625 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700626 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700627 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700628
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700629 if (type->type == ResourceType::kAttr) {
630 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800631 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700632 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800633 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800634 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700635 return false;
636 }
637 }
638 }
639
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000640 if (out != nullptr && type->type == ResourceType::kStyleable &&
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
642 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800643 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700644 class_def->GetCommentBuilder()->AppendComment("@doconly");
645 }
646
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000647 if (out != nullptr) {
648 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
649 r_class.AddMember(std::move(class_def));
650 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700651 }
652 }
653
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800654 if (rewrite_method != nullptr) {
655 r_class.AddMember(std::move(rewrite_method));
656 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700657
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000658 if (out != nullptr) {
659 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
660 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
661 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700662 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800663}
664
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700665} // namespace aapt