blob: db1561e17f169d44e7776f15030f7de8f6598d33 [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
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800259 // Look up the symbol so that we can write out in the comments what are possible legal values
260 // for this attribute.
261 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700262 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800263 // Copy the symbol data structure because the returned instance can be destroyed.
264 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700265 }
266 sorted_attributes.push_back(std::move(styleable_attr));
267 }
268
269 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800270 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700271
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800272 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
273 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700274 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000275 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800277 if (!styleable.GetComment().empty()) {
278 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800279 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800280 // Apply a default intro comment if the styleable has no comments of its own.
281 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800282 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700283
284 styleable_comment << "<p>Includes the following attributes:</p>\n"
285 "<table>\n"
286 "<colgroup align=\"left\" />\n"
287 "<colgroup align=\"left\" />\n"
288 "<tr><th>Attribute</th><th>Description</th></tr>\n";
289
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800290 // Build the table of attributes with their links and names.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700291 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800292 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700293 continue;
294 }
295
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800296 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700297 if (attr_comment_line.contains("@removed")) {
298 // Removed attributes are public but hidden from the documentation, so
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800299 // don't emit them as part of the class documentation.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700300 continue;
301 }
302
303 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700304 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
305 << (!attr_name.package.empty() ? attr_name.package
306 : context_->GetCompilationPackage())
307 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700308
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800309 // Only use the comment up until the first '.'. This is to stay compatible with
310 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700311 // annotations like @hide which would affect this Styleable).
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700312 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
313 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700314 }
315 styleable_comment << "</table>\n";
316
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800317 // Generate the @see lines for each attribute.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700318 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800319 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700320 continue;
321 }
322 styleable_comment << "@see #" << entry.field_name << "\n";
323 }
324
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800325 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326 }
327
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800328 if (r_txt_printer != nullptr) {
329 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700330 }
331
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700332 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700333 for (size_t i = 0; i < attr_count; i++) {
334 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800335 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700336
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800337 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700338 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800339 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700340 }
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800341 r_txt_printer->Print(" ").Print(id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700342 }
343 }
344
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800345 if (r_txt_printer != nullptr) {
346 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700347 }
348
349 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800350 if (out_class_def != nullptr) {
351 out_class_def->AddMember(std::move(array_def));
352 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700353
354 // Now we emit the indices into the array.
355 for (size_t i = 0; i < attr_count; i++) {
356 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800357 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700358 continue;
359 }
360
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000361 if (out_class_def != nullptr) {
362 StringPiece comment = styleable_attr.attr_ref->GetComment();
363 if (styleable_attr.symbol.value().attribute && comment.empty()) {
364 comment = styleable_attr.symbol.value().attribute->GetComment();
365 }
366
367 if (comment.contains("@removed")) {
368 // Removed attributes are public but hidden from the documentation, so
369 // don't emit them as part of the class documentation.
370 continue;
371 }
372
373 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
374
375 StringPiece package_name = attr_name.package;
376 if (package_name.empty()) {
377 package_name = context_->GetCompilationPackage();
378 }
379
380 std::unique_ptr<IntMember> index_member =
381 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
382
383 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
384
385 if (!comment.empty()) {
386 attr_processor->AppendComment("<p>\n@attr description");
387 attr_processor->AppendComment(comment);
388 } else {
389 std::stringstream default_comment;
390 default_comment << "<p>This symbol is the offset where the "
391 << "{@link " << package_name << ".R.attr#"
392 << TransformToFieldName(attr_name.entry) << "}\n"
393 << "attribute's value can be found in the "
394 << "{@link #" << array_field_name << "} array.";
395 attr_processor->AppendComment(default_comment.str());
396 }
397
398 attr_processor->AppendNewLine();
399 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
400 attr_processor->AppendNewLine();
401 attr_processor->AppendComment(
402 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
403
404 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700405 }
406
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800407 if (r_txt_printer != nullptr) {
408 r_txt_printer->Println(
409 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700410 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800411 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700412
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800413 // If there is a rewrite method to generate, add the statements that rewrite package IDs
414 // for this styleable.
415 if (out_rewrite_method != nullptr) {
416 out_rewrite_method->AppendStatement(
417 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
418 out_rewrite_method->AppendStatement(
419 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
420 out_rewrite_method->AppendStatement(
421 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
422 array_field_name.data(), array_field_name.data()));
423 out_rewrite_method->AppendStatement(" }");
424 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700425 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800426}
427
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800428void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
429 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700430 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800431 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700432 ResourceId real_id = id;
433 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
434 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800435 // Workaround for feature splits using package IDs > 0x7F.
436 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700437 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
438 }
439
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800440 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000441 if (out_class_def != nullptr) {
442 std::unique_ptr<ResourceMember> resource_member =
443 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800444
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000445 // Build the comments and annotations for this entry.
446 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800447
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000448 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800449 if (entry.visibility.level != Visibility::Level::kUndefined) {
450 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800451 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800452
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000453 // Add the comments from all configurations of this entry.
454 for (const auto& config_value : entry.values) {
455 processor->AppendComment(config_value->value->GetComment());
456 }
457
458 // If this is an Attribute, append the format Javadoc.
459 if (!entry.values.empty()) {
460 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
461 // We list out the available values for the given attribute.
462 AddAttributeFormatDoc(processor, attr);
463 }
464 }
465
466 out_class_def->AddMember(std::move(resource_member));
467 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800468
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800469 if (r_txt_printer != nullptr) {
470 r_txt_printer->Print("int ")
471 .Print(to_string(name.type))
472 .Print(" ")
473 .Print(field_name)
474 .Print(" ")
475 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700476 }
477
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800478 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700479 const StringPiece& type_str = to_string(name.type);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800480 out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
481 type_str.data(), field_name.data(),
482 type_str.data(), field_name.data()));
483 }
484}
485
486Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
487 const StringPiece& package_name_to_generate,
488 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800489 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800490 return {};
491 }
492
493 std::string unmangled_package;
494 std::string unmangled_name = entry.name;
495 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
496 // The entry name was mangled, and we successfully unmangled it.
497 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700498 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800499 // Skip the entry if it doesn't belong to the package we're writing.
500 return {};
501 }
502 } else if (package_name_to_generate != package_name) {
503 // We are processing a mangled package name,
504 // but this is a non-mangled resource.
505 return {};
506 }
507 return {std::move(unmangled_name)};
508}
509
510bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
511 const ResourceTablePackage& package,
512 const ResourceTableType& type,
513 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700514 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800515 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800516 for (const auto& entry : type.entries) {
517 const Maybe<std::string> unmangled_name =
518 UnmangleResource(package.name, package_name_to_generate, *entry);
519 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700520 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800521 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700522
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800523 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700524 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800525 if (package.id && type.id && entry->id) {
526 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800527 }
528
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800529 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
530 const ResourceNameRef resource_name(
531 package_name_to_generate,
532 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
533 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800534
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800535 // Check to see if the unmangled name is a valid Java name (not a keyword).
536 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700537 std::stringstream err;
538 err << "invalid symbol name '" << resource_name << "'";
539 error_ = err.str();
540 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700541 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700542
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800543 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700544 CHECK(!entry->values.empty());
545
546 const Styleable* styleable =
547 static_cast<const Styleable*>(entry->values.front()->value.get());
548
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800549 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800550 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700551 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700552 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800553 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700554 }
555 }
556 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700557}
558
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800559bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
560 OutputStream* out_r_txt) {
561 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700562}
563
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800564static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
565 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566 for (const std::string& annotation : annotations) {
567 std::string proper_annotation = "@";
568 proper_annotation += annotation;
569 processor->AppendComment(proper_annotation);
570 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700571}
572
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700573bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800574 const StringPiece& out_package_name, OutputStream* out,
575 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800576 ClassDefinition r_class("R", ClassQualifier::kNone, true);
577 std::unique_ptr<MethodDefinition> rewrite_method;
578
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800579 std::unique_ptr<Printer> r_txt_printer;
580 if (out_r_txt != nullptr) {
581 r_txt_printer = util::make_unique<Printer>(out_r_txt);
582 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800583 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000584 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800585 rewrite_method =
586 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800587 for (const std::string& package_to_callback :
588 options_.rewrite_callback_options.value().packages_to_callback) {
589 rewrite_method->AppendStatement(
590 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
591 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800592 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700593
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700594 for (const auto& package : table_->packages) {
595 for (const auto& type : package->types) {
596 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800597 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700598 continue;
599 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800600
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700601 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700602 const bool force_creation_if_empty =
603 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800604
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000605 std::unique_ptr<ClassDefinition> class_def;
606 if (out != nullptr) {
607 class_def = util::make_unique<ClassDefinition>(
608 to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
609 }
610
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800611 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800612 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700613 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700614 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700615
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700616 if (type->type == ResourceType::kAttr) {
617 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800618 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700619 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800620 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800621 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700622 return false;
623 }
624 }
625 }
626
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000627 if (out != nullptr && type->type == ResourceType::kStyleable &&
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700628 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
629 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800630 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700631 class_def->GetCommentBuilder()->AppendComment("@doconly");
632 }
633
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000634 if (out != nullptr) {
635 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
636 r_class.AddMember(std::move(class_def));
637 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700638 }
639 }
640
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800641 if (rewrite_method != nullptr) {
642 r_class.AddMember(std::move(rewrite_method));
643 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700644
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000645 if (out != nullptr) {
646 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
647 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
648 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700649 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800650}
651
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700652} // namespace aapt