blob: 6b07b1e96261c03bc93e7b64dbd25142ef31f98a [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.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800350 out_class_def->AddMember(std::move(array_def));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700351
352 // Now we emit the indices into the array.
353 for (size_t i = 0; i < attr_count; i++) {
354 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800355 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700356 continue;
357 }
358
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000359 if (out_class_def != nullptr) {
360 StringPiece comment = styleable_attr.attr_ref->GetComment();
361 if (styleable_attr.symbol.value().attribute && comment.empty()) {
362 comment = styleable_attr.symbol.value().attribute->GetComment();
363 }
364
365 if (comment.contains("@removed")) {
366 // Removed attributes are public but hidden from the documentation, so
367 // don't emit them as part of the class documentation.
368 continue;
369 }
370
371 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
372
373 StringPiece package_name = attr_name.package;
374 if (package_name.empty()) {
375 package_name = context_->GetCompilationPackage();
376 }
377
378 std::unique_ptr<IntMember> index_member =
379 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
380
381 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
382
383 if (!comment.empty()) {
384 attr_processor->AppendComment("<p>\n@attr description");
385 attr_processor->AppendComment(comment);
386 } else {
387 std::stringstream default_comment;
388 default_comment << "<p>This symbol is the offset where the "
389 << "{@link " << package_name << ".R.attr#"
390 << TransformToFieldName(attr_name.entry) << "}\n"
391 << "attribute's value can be found in the "
392 << "{@link #" << array_field_name << "} array.";
393 attr_processor->AppendComment(default_comment.str());
394 }
395
396 attr_processor->AppendNewLine();
397 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
398 attr_processor->AppendNewLine();
399 attr_processor->AppendComment(
400 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
401
402 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700403 }
404
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800405 if (r_txt_printer != nullptr) {
406 r_txt_printer->Println(
407 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700408 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800409 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700410
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800411 // If there is a rewrite method to generate, add the statements that rewrite package IDs
412 // for this styleable.
413 if (out_rewrite_method != nullptr) {
414 out_rewrite_method->AppendStatement(
415 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
416 out_rewrite_method->AppendStatement(
417 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
418 out_rewrite_method->AppendStatement(
419 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
420 array_field_name.data(), array_field_name.data()));
421 out_rewrite_method->AppendStatement(" }");
422 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700423 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800424}
425
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800426void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
427 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700428 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800429 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700430 ResourceId real_id = id;
431 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
432 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800433 // Workaround for feature splits using package IDs > 0x7F.
434 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700435 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
436 }
437
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800438 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000439 if (out_class_def != nullptr) {
440 std::unique_ptr<ResourceMember> resource_member =
441 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800442
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000443 // Build the comments and annotations for this entry.
444 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800445
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000446 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800447 if (entry.visibility.level != Visibility::Level::kUndefined) {
448 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800449 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800450
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000451 // Add the comments from all configurations of this entry.
452 for (const auto& config_value : entry.values) {
453 processor->AppendComment(config_value->value->GetComment());
454 }
455
456 // If this is an Attribute, append the format Javadoc.
457 if (!entry.values.empty()) {
458 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
459 // We list out the available values for the given attribute.
460 AddAttributeFormatDoc(processor, attr);
461 }
462 }
463
464 out_class_def->AddMember(std::move(resource_member));
465 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800466
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800467 if (r_txt_printer != nullptr) {
468 r_txt_printer->Print("int ")
469 .Print(to_string(name.type))
470 .Print(" ")
471 .Print(field_name)
472 .Print(" ")
473 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700474 }
475
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800476 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700477 const StringPiece& type_str = to_string(name.type);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800478 out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
479 type_str.data(), field_name.data(),
480 type_str.data(), field_name.data()));
481 }
482}
483
484Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
485 const StringPiece& package_name_to_generate,
486 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800487 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800488 return {};
489 }
490
491 std::string unmangled_package;
492 std::string unmangled_name = entry.name;
493 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
494 // The entry name was mangled, and we successfully unmangled it.
495 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700496 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800497 // Skip the entry if it doesn't belong to the package we're writing.
498 return {};
499 }
500 } else if (package_name_to_generate != package_name) {
501 // We are processing a mangled package name,
502 // but this is a non-mangled resource.
503 return {};
504 }
505 return {std::move(unmangled_name)};
506}
507
508bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
509 const ResourceTablePackage& package,
510 const ResourceTableType& type,
511 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700512 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800513 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800514 for (const auto& entry : type.entries) {
515 const Maybe<std::string> unmangled_name =
516 UnmangleResource(package.name, package_name_to_generate, *entry);
517 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700518 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800519 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700520
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800521 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700522 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800523 if (package.id && type.id && entry->id) {
524 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800525 }
526
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800527 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
528 const ResourceNameRef resource_name(
529 package_name_to_generate,
530 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
531 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800532
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800533 // Check to see if the unmangled name is a valid Java name (not a keyword).
534 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700535 std::stringstream err;
536 err << "invalid symbol name '" << resource_name << "'";
537 error_ = err.str();
538 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700539 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800541 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700542 CHECK(!entry->values.empty());
543
544 const Styleable* styleable =
545 static_cast<const Styleable*>(entry->values.front()->value.get());
546
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800547 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800548 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700549 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700550 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800551 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700552 }
553 }
554 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700555}
556
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800557bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
558 OutputStream* out_r_txt) {
559 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700560}
561
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800562static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
563 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700564 for (const std::string& annotation : annotations) {
565 std::string proper_annotation = "@";
566 proper_annotation += annotation;
567 processor->AppendComment(proper_annotation);
568 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700569}
570
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700571bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800572 const StringPiece& out_package_name, OutputStream* out,
573 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800574 ClassDefinition r_class("R", ClassQualifier::kNone, true);
575 std::unique_ptr<MethodDefinition> rewrite_method;
576
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800577 std::unique_ptr<Printer> r_txt_printer;
578 if (out_r_txt != nullptr) {
579 r_txt_printer = util::make_unique<Printer>(out_r_txt);
580 }
581
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800582 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000583 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800584 rewrite_method =
585 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800586 for (const std::string& package_to_callback :
587 options_.rewrite_callback_options.value().packages_to_callback) {
588 rewrite_method->AppendStatement(
589 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
590 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800591 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700592
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700593 for (const auto& package : table_->packages) {
594 for (const auto& type : package->types) {
595 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800596 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700597 continue;
598 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800599
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700600 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700601 const bool force_creation_if_empty =
602 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800603
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000604 std::unique_ptr<ClassDefinition> class_def;
605 if (out != nullptr) {
606 class_def = util::make_unique<ClassDefinition>(
607 to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
608 }
609
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800610 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800611 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700612 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700613 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700614
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700615 if (type->type == ResourceType::kAttr) {
616 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800617 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700618 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800619 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800620 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700621 return false;
622 }
623 }
624 }
625
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000626 if (out != nullptr && type->type == ResourceType::kStyleable &&
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700627 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
628 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800629 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700630 class_def->GetCommentBuilder()->AppendComment("@doconly");
631 }
632
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000633 if (out != nullptr) {
634 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
635 r_class.AddMember(std::move(class_def));
636 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700637 }
638 }
639
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800640 if (rewrite_method != nullptr) {
641 r_class.AddMember(std::move(rewrite_method));
642 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700643
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000644 if (out != nullptr) {
645 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
646 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
647 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700648 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800649}
650
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700651} // namespace aapt