blob: d1a70a75a44e0b687f386bac44eede00d297eb93 [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
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800301 // Build the table of attributes with their links and names.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700302 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800303 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700304 continue;
305 }
306
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800307 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700308 if (attr_comment_line.contains("@removed")) {
309 // Removed attributes are public but hidden from the documentation, so
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800310 // don't emit them as part of the class documentation.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700311 continue;
312 }
313
314 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).
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700323 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
324 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700325 }
326 styleable_comment << "</table>\n";
327
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800328 // Generate the @see lines for each attribute.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700329 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800330 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700331 continue;
332 }
333 styleable_comment << "@see #" << entry.field_name << "\n";
334 }
335
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800336 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700337 }
338
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800339 if (r_txt_printer != nullptr) {
340 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700341 }
342
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700343 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700344 for (size_t i = 0; i < attr_count; i++) {
345 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800346 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700347
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800348 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700349 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800350 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700351 }
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800352 r_txt_printer->Print(" ").Print(id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700353 }
354 }
355
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800356 if (r_txt_printer != nullptr) {
357 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700358 }
359
360 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800361 if (out_class_def != nullptr) {
362 out_class_def->AddMember(std::move(array_def));
363 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700364
365 // Now we emit the indices into the array.
366 for (size_t i = 0; i < attr_count; i++) {
367 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800368 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700369 continue;
370 }
371
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000372 if (out_class_def != nullptr) {
373 StringPiece comment = styleable_attr.attr_ref->GetComment();
374 if (styleable_attr.symbol.value().attribute && comment.empty()) {
375 comment = styleable_attr.symbol.value().attribute->GetComment();
376 }
377
378 if (comment.contains("@removed")) {
379 // Removed attributes are public but hidden from the documentation, so
380 // don't emit them as part of the class documentation.
381 continue;
382 }
383
384 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
385
386 StringPiece package_name = attr_name.package;
387 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700388 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000389 }
390
391 std::unique_ptr<IntMember> index_member =
392 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
393
394 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
395
396 if (!comment.empty()) {
397 attr_processor->AppendComment("<p>\n@attr description");
398 attr_processor->AppendComment(comment);
399 } else {
400 std::stringstream default_comment;
401 default_comment << "<p>This symbol is the offset where the "
402 << "{@link " << package_name << ".R.attr#"
403 << TransformToFieldName(attr_name.entry) << "}\n"
404 << "attribute's value can be found in the "
405 << "{@link #" << array_field_name << "} array.";
406 attr_processor->AppendComment(default_comment.str());
407 }
408
409 attr_processor->AppendNewLine();
410 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
411 attr_processor->AppendNewLine();
412 attr_processor->AppendComment(
413 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
414
415 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700416 }
417
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800418 if (r_txt_printer != nullptr) {
419 r_txt_printer->Println(
420 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700421 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800422 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700423
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800424 // If there is a rewrite method to generate, add the statements that rewrite package IDs
425 // for this styleable.
426 if (out_rewrite_method != nullptr) {
427 out_rewrite_method->AppendStatement(
428 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
429 out_rewrite_method->AppendStatement(
430 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
431 out_rewrite_method->AppendStatement(
432 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
433 array_field_name.data(), array_field_name.data()));
434 out_rewrite_method->AppendStatement(" }");
435 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700436 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800437}
438
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800439void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
440 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700441 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800442 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700443 ResourceId real_id = id;
444 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
445 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800446 // Workaround for feature splits using package IDs > 0x7F.
447 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700448 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
449 }
450
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800451 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000452 if (out_class_def != nullptr) {
453 std::unique_ptr<ResourceMember> resource_member =
454 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800455
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000456 // Build the comments and annotations for this entry.
457 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800458
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000459 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800460 if (entry.visibility.level != Visibility::Level::kUndefined) {
461 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800462 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800463
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000464 // Add the comments from all configurations of this entry.
465 for (const auto& config_value : entry.values) {
466 processor->AppendComment(config_value->value->GetComment());
467 }
468
469 // If this is an Attribute, append the format Javadoc.
470 if (!entry.values.empty()) {
471 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
472 // We list out the available values for the given attribute.
473 AddAttributeFormatDoc(processor, attr);
474 }
475 }
476
477 out_class_def->AddMember(std::move(resource_member));
478 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800479
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800480 if (r_txt_printer != nullptr) {
481 r_txt_printer->Print("int ")
482 .Print(to_string(name.type))
483 .Print(" ")
484 .Print(field_name)
485 .Print(" ")
486 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700487 }
488
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800489 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700490 const StringPiece& type_str = to_string(name.type);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800491 out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
492 type_str.data(), field_name.data(),
493 type_str.data(), field_name.data()));
494 }
495}
496
497Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
498 const StringPiece& package_name_to_generate,
499 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800500 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800501 return {};
502 }
503
504 std::string unmangled_package;
505 std::string unmangled_name = entry.name;
506 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
507 // The entry name was mangled, and we successfully unmangled it.
508 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700509 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800510 // Skip the entry if it doesn't belong to the package we're writing.
511 return {};
512 }
513 } else if (package_name_to_generate != package_name) {
514 // We are processing a mangled package name,
515 // but this is a non-mangled resource.
516 return {};
517 }
518 return {std::move(unmangled_name)};
519}
520
521bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
522 const ResourceTablePackage& package,
523 const ResourceTableType& type,
524 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700525 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800526 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800527 for (const auto& entry : type.entries) {
528 const Maybe<std::string> unmangled_name =
529 UnmangleResource(package.name, package_name_to_generate, *entry);
530 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700531 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800532 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700533
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800534 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700535 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800536 if (package.id && type.id && entry->id) {
537 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800538 }
539
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800540 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
541 const ResourceNameRef resource_name(
542 package_name_to_generate,
543 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
544 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800545
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800546 // Check to see if the unmangled name is a valid Java name (not a keyword).
547 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700548 std::stringstream err;
549 err << "invalid symbol name '" << resource_name << "'";
550 error_ = err.str();
551 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700552 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700553
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800554 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700555 CHECK(!entry->values.empty());
556
557 const Styleable* styleable =
558 static_cast<const Styleable*>(entry->values.front()->value.get());
559
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800560 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800561 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700562 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700563 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800564 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700565 }
566 }
567 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700568}
569
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800570bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
571 OutputStream* out_r_txt) {
572 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700573}
574
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800575static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
576 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700577 for (const std::string& annotation : annotations) {
578 std::string proper_annotation = "@";
579 proper_annotation += annotation;
580 processor->AppendComment(proper_annotation);
581 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700582}
583
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700584bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800585 const StringPiece& out_package_name, OutputStream* out,
586 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800587 ClassDefinition r_class("R", ClassQualifier::kNone, true);
588 std::unique_ptr<MethodDefinition> rewrite_method;
589
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800590 std::unique_ptr<Printer> r_txt_printer;
591 if (out_r_txt != nullptr) {
592 r_txt_printer = util::make_unique<Printer>(out_r_txt);
593 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800594 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000595 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800596 rewrite_method =
597 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800598 for (const std::string& package_to_callback :
599 options_.rewrite_callback_options.value().packages_to_callback) {
600 rewrite_method->AppendStatement(
601 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
602 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800603 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700604
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700605 for (const auto& package : table_->packages) {
606 for (const auto& type : package->types) {
607 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800608 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700609 continue;
610 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800611
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700612 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700613 const bool force_creation_if_empty =
614 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
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
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000638 if (out != nullptr && type->type == ResourceType::kStyleable &&
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700639 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
640 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800641 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700642 class_def->GetCommentBuilder()->AppendComment("@doconly");
643 }
644
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000645 if (out != nullptr) {
646 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
647 r_class.AddMember(std::move(class_def));
648 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700649 }
650 }
651
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800652 if (rewrite_method != nullptr) {
653 r_class.AddMember(std::move(rewrite_method));
654 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700655
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000656 if (out != nullptr) {
657 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
658 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
659 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700660 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800661}
662
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700663} // namespace aapt