blob: 31d205e1b9c92d167b92e1ebd396f5db337de469 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Lesinskica5638f2015-10-21 14:42:43 -070017#include "java/JavaClassGenerator.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080018
Adam Lesinskica2fc352015-04-03 12:08:26 -070019#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080020#include <ostream>
21#include <set>
22#include <sstream>
23#include <tuple>
24
Adam Lesinski418763f2017-04-11 17:36:53 -070025#include "android-base/errors.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070026#include "android-base/logging.h"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080027#include "android-base/stringprintf.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080028#include "androidfw/StringPiece.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070029
30#include "NameMangler.h"
31#include "Resource.h"
32#include "ResourceTable.h"
33#include "ResourceValues.h"
Adam Lesinski1e4b0e52017-04-27 15:01:10 -070034#include "SdkConstants.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070035#include "ValueVisitor.h"
36#include "java/AnnotationProcessor.h"
37#include "java/ClassDefinition.h"
38#include "process/SymbolTable.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080039
Adam Lesinskia693c4a2017-11-09 11:29:39 -080040using ::aapt::io::OutputStream;
41using ::aapt::text::Printer;
42using ::android::StringPiece;
43using ::android::base::StringPrintf;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070044
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080045namespace aapt {
46
Adam Lesinskid0f116b2016-07-08 15:00:32 -070047static const std::set<StringPiece> sJavaIdentifiers = {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070048 "abstract", "assert", "boolean", "break", "byte",
49 "case", "catch", "char", "class", "const",
50 "continue", "default", "do", "double", "else",
51 "enum", "extends", "final", "finally", "float",
52 "for", "goto", "if", "implements", "import",
53 "instanceof", "int", "interface", "long", "native",
54 "new", "package", "private", "protected", "public",
55 "return", "short", "static", "strictfp", "super",
56 "switch", "synchronized", "this", "throw", "throws",
57 "transient", "try", "void", "volatile", "while",
58 "true", "false", "null"};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080059
Adam Lesinskice5e56e2016-10-21 17:56:45 -070060static bool IsValidSymbol(const StringPiece& symbol) {
61 return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080062}
63
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080064// Java symbols can not contain . or -, but those are valid in a resource name.
65// Replace those with '_'.
Adam Koskidc21dea2017-07-21 10:55:27 -070066std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
Adam Lesinskid5083f62017-01-16 15:07:21 -080067 std::string output = symbol.to_string();
Adam Lesinskice5e56e2016-10-21 17:56:45 -070068 for (char& c : output) {
69 if (c == '.' || c == '-') {
70 c = '_';
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080071 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070072 }
73 return output;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080074}
75
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080076// Transforms an attribute in a styleable to the Java field name:
77//
78// <declare-styleable name="Foo">
79// <attr name="android:bar" />
80// <attr name="bar" />
81// </declare-styleable>
82//
83// Foo_android_bar
84// Foo_bar
85static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
86 const std::string& styleable_class_name,
87 const StringPiece& package_name_to_generate) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070088 std::string output = styleable_class_name;
Adam Lesinski74605cd2016-03-03 15:39:50 -080089
Adam Lesinskice5e56e2016-10-21 17:56:45 -070090 // We may reference IDs from other packages, so prefix the entry name with
91 // the package.
92 if (!attr_name.package.empty() &&
93 package_name_to_generate != attr_name.package) {
Adam Koskidc21dea2017-07-21 10:55:27 -070094 output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070095 }
Adam Koskidc21dea2017-07-21 10:55:27 -070096 output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070097 return output;
Adam Lesinski74605cd2016-03-03 15:39:50 -080098}
99
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800100static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700101 const uint32_t type_mask = attr->type_mask;
102 if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
103 processor->AppendComment(
104 "<p>May be a reference to another resource, in the form\n"
105 "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a "
106 "theme\n"
107 "attribute in the form\n"
108 "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
109 }
110
111 if (type_mask & android::ResTable_map::TYPE_STRING) {
112 processor->AppendComment(
113 "<p>May be a string value, using '\\\\;' to escape characters such as\n"
114 "'\\\\n' or '\\\\uxxxx' for a unicode character;");
115 }
116
117 if (type_mask & android::ResTable_map::TYPE_INTEGER) {
118 processor->AppendComment(
119 "<p>May be an integer value, such as \"<code>100</code>\".");
120 }
121
122 if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
123 processor->AppendComment(
124 "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
125 "\"<code>false</code>\".");
126 }
127
128 if (type_mask & android::ResTable_map::TYPE_COLOR) {
129 processor->AppendComment(
130 "<p>May be a color value, in the form of "
131 "\"<code>#<i>rgb</i></code>\",\n"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800132 "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700133 "\"<code>#<i>aarrggbb</i></code>\".");
134 }
135
136 if (type_mask & android::ResTable_map::TYPE_FLOAT) {
137 processor->AppendComment(
138 "<p>May be a floating point value, such as \"<code>1.2</code>\".");
139 }
140
141 if (type_mask & android::ResTable_map::TYPE_DIMENSION) {
142 processor->AppendComment(
143 "<p>May be a dimension value, which is a floating point number "
144 "appended with a\n"
145 "unit such as \"<code>14.5sp</code>\".\n"
146 "Available units are: px (pixels), dp (density-independent pixels),\n"
147 "sp (scaled pixels based on preferred font size), in (inches), and\n"
148 "mm (millimeters).");
149 }
150
151 if (type_mask & android::ResTable_map::TYPE_FRACTION) {
152 processor->AppendComment(
153 "<p>May be a fractional value, which is a floating point number "
154 "appended with\n"
155 "either % or %p, such as \"<code>14.5%</code>\".\n"
156 "The % suffix always means a percentage of the base size;\n"
157 "the optional %p suffix provides a size relative to some parent "
158 "container.");
159 }
160
161 if (type_mask &
162 (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
163 if (type_mask & android::ResTable_map::TYPE_FLAGS) {
164 processor->AppendComment(
165 "<p>Must be one or more (separated by '|') of the following "
166 "constant values.</p>");
167 } else {
168 processor->AppendComment(
169 "<p>Must be one of the following constant values.</p>");
Adam Lesinski76565542016-03-10 21:55:04 -0800170 }
171
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 processor->AppendComment(
173 "<table>\n<colgroup align=\"left\" />\n"
174 "<colgroup align=\"left\" />\n"
175 "<colgroup align=\"left\" />\n"
176 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
177 for (const Attribute::Symbol& symbol : attr->symbols) {
178 std::stringstream line;
179 line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
180 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
181 << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
182 << "</td></tr>";
183 processor->AppendComment(line.str());
Adam Lesinski76565542016-03-10 21:55:04 -0800184 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700185 processor->AppendComment("</table>");
186 }
Adam Lesinski76565542016-03-10 21:55:04 -0800187}
188
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700189JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
190 ResourceTable* table,
191 const JavaClassGeneratorOptions& options)
192 : context_(context), table_(table), options_(options) {}
193
Adam Lesinski71be7052017-12-12 16:48:07 -0800194bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700195 switch (options_.types) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700196 case JavaClassGeneratorOptions::SymbolTypes::kAll:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700197 return false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700198 case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
Adam Lesinski71be7052017-12-12 16:48:07 -0800199 return level == Visibility::Level::kUndefined;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700200 case JavaClassGeneratorOptions::SymbolTypes::kPublic:
Adam Lesinski71be7052017-12-12 16:48:07 -0800201 return level != Visibility::Level::kPublic;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700202 }
203 return true;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700204}
205
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800206// Whether or not to skip writing this symbol.
207bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) {
208 return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
209 !symbol.value().is_public);
210}
211
Adam Lesinski74605cd2016-03-03 15:39:50 -0800212struct StyleableAttr {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800213 const Reference* attr_ref = nullptr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700214 std::string field_name;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800215 Maybe<SymbolTable::Symbol> symbol;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800216};
217
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800218static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
219 const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
220 const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700221 if (lhs_id < rhs_id) {
222 return true;
223 } else if (lhs_id > rhs_id) {
224 return false;
225 } else {
226 return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
227 }
228}
229
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800230void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
231 const Styleable& styleable,
232 const StringPiece& package_name_to_generate,
233 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700234 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800235 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800236 const std::string array_field_name = TransformToFieldName(name.entry);
237 std::unique_ptr<ResourceArrayMember> array_def =
238 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700239
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800240 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700241 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800242 sorted_attributes.reserve(styleable.entries.size());
243 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700244 // If we are not encoding final attributes, the styleable entry may have no
245 // ID if we are building a static library.
246 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
247 CHECK(bool(attr.name)) << "no name set for Styleable entry";
248
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800249 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700250 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800251 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700252 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700253
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800254 // The field name for this attribute is prefixed by the name of this styleable and
255 // the package it comes from.
256 styleable_attr.field_name =
257 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700259 Reference ref = attr;
260 if (attr.name.value().package.empty()) {
261
262 // If the resource does not have a package name, set the package to the unmangled package name
263 // of the styleable declaration because attributes without package names would have been
264 // declared in the same package as the styleable.
265 ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
266 ref.name.value().entry);
267 }
268
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800269 // Look up the symbol so that we can write out in the comments what are possible legal values
270 // for this attribute.
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700271 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
272
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800274 // Copy the symbol data structure because the returned instance can be destroyed.
275 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 }
277 sorted_attributes.push_back(std::move(styleable_attr));
278 }
279
280 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800281 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700282
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800283 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
284 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700285 const size_t attr_count = sorted_attributes.size();
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000286 if (out_class_def != nullptr && attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700287 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800288 if (!styleable.GetComment().empty()) {
289 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800290 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800291 // Apply a default intro comment if the styleable has no comments of its own.
292 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800293 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700294
295 styleable_comment << "<p>Includes the following attributes:</p>\n"
296 "<table>\n"
297 "<colgroup align=\"left\" />\n"
298 "<colgroup align=\"left\" />\n"
299 "<tr><th>Attribute</th><th>Description</th></tr>\n";
300
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800301 // Removed and hidden attributes are public but hidden from the documentation, so don't emit
302 // them as part of the class documentation.
303 std::vector<StyleableAttr> documentation_attrs = sorted_attributes;
304 auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
305 documentation_attrs.end(),
306 [&](StyleableAttr entry) -> bool {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800307 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800308 return SkipSymbol(entry.symbol) || attr_comment_line.contains("@removed")
309 || attr_comment_line.contains("@hide");
310 });
311 documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700312
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800313 // Build the table of attributes with their links and names.
314 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700315 const ResourceName& attr_name = entry.attr_ref->name.value();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700316 styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
317 << (!attr_name.package.empty() ? attr_name.package
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700318 : package_name_to_generate)
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700319 << ":" << attr_name.entry << "}</code></td>";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700320
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800321 // Only use the comment up until the first '.'. This is to stay compatible with
322 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 // annotations like @hide which would affect this Styleable).
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800324 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskie967d3f2017-07-24 18:19:36 -0700325 styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line)
326 << "</td></tr>\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700327 }
328 styleable_comment << "</table>\n";
329
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800330 // Generate the @see lines for each attribute.
Ryan Mitchellbe8607d2018-12-03 11:28:42 -0800331 for (const StyleableAttr& entry : documentation_attrs) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700332 styleable_comment << "@see #" << entry.field_name << "\n";
333 }
334
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800335 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700336 }
337
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800338 if (r_txt_printer != nullptr) {
339 r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
Adam Lesinski418763f2017-04-11 17:36:53 -0700340 }
341
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700342 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700343 for (size_t i = 0; i < attr_count; i++) {
344 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800345 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700346
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800347 if (r_txt_printer != nullptr) {
Adam Lesinski418763f2017-04-11 17:36:53 -0700348 if (i != 0) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800349 r_txt_printer->Print(",");
Adam Lesinski418763f2017-04-11 17:36:53 -0700350 }
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800351 r_txt_printer->Print(" ").Print(id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700352 }
353 }
354
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800355 if (r_txt_printer != nullptr) {
356 r_txt_printer->Println(" }");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700357 }
358
359 // Add the Styleable array to the Styleable class.
Todd Kennedy949b62532018-03-02 14:19:45 -0800360 if (out_class_def != nullptr) {
361 out_class_def->AddMember(std::move(array_def));
362 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700363
364 // Now we emit the indices into the array.
365 for (size_t i = 0; i < attr_count; i++) {
366 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800367 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700368 continue;
369 }
370
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000371 if (out_class_def != nullptr) {
372 StringPiece comment = styleable_attr.attr_ref->GetComment();
373 if (styleable_attr.symbol.value().attribute && comment.empty()) {
374 comment = styleable_attr.symbol.value().attribute->GetComment();
375 }
376
377 if (comment.contains("@removed")) {
378 // Removed attributes are public but hidden from the documentation, so
379 // don't emit them as part of the class documentation.
380 continue;
381 }
382
383 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
384
385 StringPiece package_name = attr_name.package;
386 if (package_name.empty()) {
Ryan Mitchell23cc5d52018-07-12 17:16:40 -0700387 package_name = package_name_to_generate;
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000388 }
389
390 std::unique_ptr<IntMember> index_member =
391 util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i));
392
393 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
394
395 if (!comment.empty()) {
396 attr_processor->AppendComment("<p>\n@attr description");
397 attr_processor->AppendComment(comment);
398 } else {
399 std::stringstream default_comment;
400 default_comment << "<p>This symbol is the offset where the "
401 << "{@link " << package_name << ".R.attr#"
402 << TransformToFieldName(attr_name.entry) << "}\n"
403 << "attribute's value can be found in the "
404 << "{@link #" << array_field_name << "} array.";
405 attr_processor->AppendComment(default_comment.str());
406 }
407
408 attr_processor->AppendNewLine();
409 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
410 attr_processor->AppendNewLine();
411 attr_processor->AppendComment(
412 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
413
414 out_class_def->AddMember(std::move(index_member));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700415 }
416
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800417 if (r_txt_printer != nullptr) {
418 r_txt_printer->Println(
419 StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
Adam Lesinski418763f2017-04-11 17:36:53 -0700420 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800421 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700422
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800423 // If there is a rewrite method to generate, add the statements that rewrite package IDs
424 // for this styleable.
425 if (out_rewrite_method != nullptr) {
426 out_rewrite_method->AppendStatement(
427 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
428 out_rewrite_method->AppendStatement(
429 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
430 out_rewrite_method->AppendStatement(
431 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
432 array_field_name.data(), array_field_name.data()));
433 out_rewrite_method->AppendStatement(" }");
434 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700435 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800436}
437
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800438void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
439 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700440 MethodDefinition* out_rewrite_method,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800441 text::Printer* r_txt_printer) {
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700442 ResourceId real_id = id;
443 if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
444 id.package_id() > kAppPackageId) {
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800445 // Workaround for feature splits using package IDs > 0x7F.
446 // See b/37498913.
Adam Lesinski1e4b0e52017-04-27 15:01:10 -0700447 real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
448 }
449
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800450 const std::string field_name = TransformToFieldName(name.entry);
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000451 if (out_class_def != nullptr) {
452 std::unique_ptr<ResourceMember> resource_member =
453 util::make_unique<ResourceMember>(field_name, real_id);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800454
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000455 // Build the comments and annotations for this entry.
456 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800457
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000458 // Add the comments from any <public> tags.
Adam Lesinski71be7052017-12-12 16:48:07 -0800459 if (entry.visibility.level != Visibility::Level::kUndefined) {
460 processor->AppendComment(entry.visibility.comment);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800461 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800462
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000463 // Add the comments from all configurations of this entry.
464 for (const auto& config_value : entry.values) {
465 processor->AppendComment(config_value->value->GetComment());
466 }
467
468 // If this is an Attribute, append the format Javadoc.
469 if (!entry.values.empty()) {
470 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
471 // We list out the available values for the given attribute.
472 AddAttributeFormatDoc(processor, attr);
473 }
474 }
475
476 out_class_def->AddMember(std::move(resource_member));
477 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800478
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800479 if (r_txt_printer != nullptr) {
480 r_txt_printer->Print("int ")
481 .Print(to_string(name.type))
482 .Print(" ")
483 .Print(field_name)
484 .Print(" ")
485 .Println(real_id.to_string());
Adam Lesinski418763f2017-04-11 17:36:53 -0700486 }
487
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800488 if (out_rewrite_method != nullptr) {
Adam Lesinski93190b72017-11-03 15:20:17 -0700489 const StringPiece& type_str = to_string(name.type);
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800490 out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
491 type_str.data(), field_name.data(),
492 type_str.data(), field_name.data()));
493 }
494}
495
496Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
497 const StringPiece& package_name_to_generate,
498 const ResourceEntry& entry) {
Adam Lesinski71be7052017-12-12 16:48:07 -0800499 if (SkipSymbol(entry.visibility.level)) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800500 return {};
501 }
502
503 std::string unmangled_package;
504 std::string unmangled_name = entry.name;
505 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
506 // The entry name was mangled, and we successfully unmangled it.
507 // Check that we want to emit this symbol.
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700508 if (package_name_to_generate != unmangled_package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800509 // Skip the entry if it doesn't belong to the package we're writing.
510 return {};
511 }
512 } else if (package_name_to_generate != package_name) {
513 // We are processing a mangled package name,
514 // but this is a non-mangled resource.
515 return {};
516 }
517 return {std::move(unmangled_name)};
518}
519
520bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
521 const ResourceTablePackage& package,
522 const ResourceTableType& type,
523 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700524 MethodDefinition* out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800525 Printer* r_txt_printer) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800526 for (const auto& entry : type.entries) {
527 const Maybe<std::string> unmangled_name =
528 UnmangleResource(package.name, package_name_to_generate, *entry);
529 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700530 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800531 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700532
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800533 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700534 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800535 if (package.id && type.id && entry->id) {
536 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800537 }
538
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800539 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
540 const ResourceNameRef resource_name(
541 package_name_to_generate,
542 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
543 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800544
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800545 // Check to see if the unmangled name is a valid Java name (not a keyword).
546 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700547 std::stringstream err;
548 err << "invalid symbol name '" << resource_name << "'";
549 error_ = err.str();
550 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700551 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700552
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800553 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700554 CHECK(!entry->values.empty());
555
556 const Styleable* styleable =
557 static_cast<const Styleable*>(entry->values.front()->value.get());
558
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800559 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800560 out_rewrite_method_def, r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700561 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700562 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800563 r_txt_printer);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700564 }
565 }
566 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700567}
568
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800569bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
570 OutputStream* out_r_txt) {
571 return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
Adam Lesinski769de982015-04-10 19:43:55 -0700572}
573
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800574static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
575 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700576 for (const std::string& annotation : annotations) {
577 std::string proper_annotation = "@";
578 proper_annotation += annotation;
579 processor->AppendComment(proper_annotation);
580 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700581}
582
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700583bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800584 const StringPiece& out_package_name, OutputStream* out,
585 OutputStream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800586 ClassDefinition r_class("R", ClassQualifier::kNone, true);
587 std::unique_ptr<MethodDefinition> rewrite_method;
588
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800589 std::unique_ptr<Printer> r_txt_printer;
590 if (out_r_txt != nullptr) {
591 r_txt_printer = util::make_unique<Printer>(out_r_txt);
592 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800593 // Generate an onResourcesLoaded() callback if requested.
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000594 if (out != nullptr && options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800595 rewrite_method =
596 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800597 for (const std::string& package_to_callback :
598 options_.rewrite_callback_options.value().packages_to_callback) {
599 rewrite_method->AppendStatement(
600 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
601 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800602 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700603
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700604 for (const auto& package : table_->packages) {
605 for (const auto& type : package->types) {
606 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800607 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700608 continue;
609 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800610
Adam Lesinski1ef0fa92017-08-15 21:32:49 -0700611 // Stay consistent with AAPT and generate an empty type class if the R class is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700612 const bool force_creation_if_empty =
613 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800614
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000615 std::unique_ptr<ClassDefinition> class_def;
616 if (out != nullptr) {
617 class_def = util::make_unique<ClassDefinition>(
618 to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
619 }
620
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800621 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800622 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700623 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700624 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700625
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700626 if (type->type == ResourceType::kAttr) {
627 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800628 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700629 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800630 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinskia693c4a2017-11-09 11:29:39 -0800631 rewrite_method.get(), r_txt_printer.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700632 return false;
633 }
634 }
635 }
636
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000637 if (out != nullptr && type->type == ResourceType::kStyleable &&
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700638 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
639 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800640 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 class_def->GetCommentBuilder()->AppendComment("@doconly");
642 }
643
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000644 if (out != nullptr) {
645 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
646 r_class.AddMember(std::move(class_def));
647 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700648 }
649 }
650
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800651 if (rewrite_method != nullptr) {
652 r_class.AddMember(std::move(rewrite_method));
653 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700654
Izabela Orlowska23a6e1e2017-12-05 14:52:07 +0000655 if (out != nullptr) {
656 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
657 ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
658 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700659 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800660}
661
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700662} // namespace aapt