blob: a8226c0a90827a85622d5e30d4efcca7ffcfe579 [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"
34#include "ValueVisitor.h"
35#include "java/AnnotationProcessor.h"
36#include "java/ClassDefinition.h"
37#include "process/SymbolTable.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080038
39using android::StringPiece;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080040using android::base::StringPrintf;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070041
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080042namespace aapt {
43
Adam Lesinskid0f116b2016-07-08 15:00:32 -070044static const std::set<StringPiece> sJavaIdentifiers = {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070045 "abstract", "assert", "boolean", "break", "byte",
46 "case", "catch", "char", "class", "const",
47 "continue", "default", "do", "double", "else",
48 "enum", "extends", "final", "finally", "float",
49 "for", "goto", "if", "implements", "import",
50 "instanceof", "int", "interface", "long", "native",
51 "new", "package", "private", "protected", "public",
52 "return", "short", "static", "strictfp", "super",
53 "switch", "synchronized", "this", "throw", "throws",
54 "transient", "try", "void", "volatile", "while",
55 "true", "false", "null"};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080056
Adam Lesinskice5e56e2016-10-21 17:56:45 -070057static bool IsValidSymbol(const StringPiece& symbol) {
58 return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080059}
60
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080061// Java symbols can not contain . or -, but those are valid in a resource name.
62// Replace those with '_'.
63static std::string TransformToFieldName(const StringPiece& symbol) {
Adam Lesinskid5083f62017-01-16 15:07:21 -080064 std::string output = symbol.to_string();
Adam Lesinskice5e56e2016-10-21 17:56:45 -070065 for (char& c : output) {
66 if (c == '.' || c == '-') {
67 c = '_';
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080068 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070069 }
70 return output;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080071}
72
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080073// Transforms an attribute in a styleable to the Java field name:
74//
75// <declare-styleable name="Foo">
76// <attr name="android:bar" />
77// <attr name="bar" />
78// </declare-styleable>
79//
80// Foo_android_bar
81// Foo_bar
82static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
83 const std::string& styleable_class_name,
84 const StringPiece& package_name_to_generate) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070085 std::string output = styleable_class_name;
Adam Lesinski74605cd2016-03-03 15:39:50 -080086
Adam Lesinskice5e56e2016-10-21 17:56:45 -070087 // We may reference IDs from other packages, so prefix the entry name with
88 // the package.
89 if (!attr_name.package.empty() &&
90 package_name_to_generate != attr_name.package) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080091 output += "_" + TransformToFieldName(attr_name.package);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070092 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080093 output += "_" + TransformToFieldName(attr_name.entry);
Adam Lesinskice5e56e2016-10-21 17:56:45 -070094 return output;
Adam Lesinski74605cd2016-03-03 15:39:50 -080095}
96
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -080097static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070098 const uint32_t type_mask = attr->type_mask;
99 if (type_mask & android::ResTable_map::TYPE_REFERENCE) {
100 processor->AppendComment(
101 "<p>May be a reference to another resource, in the form\n"
102 "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a "
103 "theme\n"
104 "attribute in the form\n"
105 "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
106 }
107
108 if (type_mask & android::ResTable_map::TYPE_STRING) {
109 processor->AppendComment(
110 "<p>May be a string value, using '\\\\;' to escape characters such as\n"
111 "'\\\\n' or '\\\\uxxxx' for a unicode character;");
112 }
113
114 if (type_mask & android::ResTable_map::TYPE_INTEGER) {
115 processor->AppendComment(
116 "<p>May be an integer value, such as \"<code>100</code>\".");
117 }
118
119 if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
120 processor->AppendComment(
121 "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
122 "\"<code>false</code>\".");
123 }
124
125 if (type_mask & android::ResTable_map::TYPE_COLOR) {
126 processor->AppendComment(
127 "<p>May be a color value, in the form of "
128 "\"<code>#<i>rgb</i></code>\",\n"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800129 "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700130 "\"<code>#<i>aarrggbb</i></code>\".");
131 }
132
133 if (type_mask & android::ResTable_map::TYPE_FLOAT) {
134 processor->AppendComment(
135 "<p>May be a floating point value, such as \"<code>1.2</code>\".");
136 }
137
138 if (type_mask & android::ResTable_map::TYPE_DIMENSION) {
139 processor->AppendComment(
140 "<p>May be a dimension value, which is a floating point number "
141 "appended with a\n"
142 "unit such as \"<code>14.5sp</code>\".\n"
143 "Available units are: px (pixels), dp (density-independent pixels),\n"
144 "sp (scaled pixels based on preferred font size), in (inches), and\n"
145 "mm (millimeters).");
146 }
147
148 if (type_mask & android::ResTable_map::TYPE_FRACTION) {
149 processor->AppendComment(
150 "<p>May be a fractional value, which is a floating point number "
151 "appended with\n"
152 "either % or %p, such as \"<code>14.5%</code>\".\n"
153 "The % suffix always means a percentage of the base size;\n"
154 "the optional %p suffix provides a size relative to some parent "
155 "container.");
156 }
157
158 if (type_mask &
159 (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
160 if (type_mask & android::ResTable_map::TYPE_FLAGS) {
161 processor->AppendComment(
162 "<p>Must be one or more (separated by '|') of the following "
163 "constant values.</p>");
164 } else {
165 processor->AppendComment(
166 "<p>Must be one of the following constant values.</p>");
Adam Lesinski76565542016-03-10 21:55:04 -0800167 }
168
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700169 processor->AppendComment(
170 "<table>\n<colgroup align=\"left\" />\n"
171 "<colgroup align=\"left\" />\n"
172 "<colgroup align=\"left\" />\n"
173 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
174 for (const Attribute::Symbol& symbol : attr->symbols) {
175 std::stringstream line;
176 line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
177 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
178 << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
179 << "</td></tr>";
180 processor->AppendComment(line.str());
Adam Lesinski76565542016-03-10 21:55:04 -0800181 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700182 processor->AppendComment("</table>");
183 }
Adam Lesinski76565542016-03-10 21:55:04 -0800184}
185
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700186JavaClassGenerator::JavaClassGenerator(IAaptContext* context,
187 ResourceTable* table,
188 const JavaClassGeneratorOptions& options)
189 : context_(context), table_(table), options_(options) {}
190
191bool JavaClassGenerator::SkipSymbol(SymbolState state) {
192 switch (options_.types) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700193 case JavaClassGeneratorOptions::SymbolTypes::kAll:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700194 return false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700195 case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700196 return state == SymbolState::kUndefined;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700197 case JavaClassGeneratorOptions::SymbolTypes::kPublic:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700198 return state != SymbolState::kPublic;
199 }
200 return true;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700201}
202
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800203// Whether or not to skip writing this symbol.
204bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) {
205 return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
206 !symbol.value().is_public);
207}
208
Adam Lesinski74605cd2016-03-03 15:39:50 -0800209struct StyleableAttr {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800210 const Reference* attr_ref = nullptr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700211 std::string field_name;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800212 Maybe<SymbolTable::Symbol> symbol;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800213};
214
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800215static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
216 const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
217 const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700218 if (lhs_id < rhs_id) {
219 return true;
220 } else if (lhs_id > rhs_id) {
221 return false;
222 } else {
223 return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
224 }
225}
226
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800227void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
228 const Styleable& styleable,
229 const StringPiece& package_name_to_generate,
230 ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700231 MethodDefinition* out_rewrite_method,
232 std::ostream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800233 const std::string array_field_name = TransformToFieldName(name.entry);
234 std::unique_ptr<ResourceArrayMember> array_def =
235 util::make_unique<ResourceArrayMember>(array_field_name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700236
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800237 // The array must be sorted by resource ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700238 std::vector<StyleableAttr> sorted_attributes;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800239 sorted_attributes.reserve(styleable.entries.size());
240 for (const auto& attr : styleable.entries) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700241 // If we are not encoding final attributes, the styleable entry may have no
242 // ID if we are building a static library.
243 CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry";
244 CHECK(bool(attr.name)) << "no name set for Styleable entry";
245
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800246 // We will need the unmangled, transformed name in the comments and the field,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700247 // so create it once and cache it in this StyleableAttr data structure.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800248 StyleableAttr styleable_attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249 styleable_attr.attr_ref = &attr;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700250
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800251 // The field name for this attribute is prefixed by the name of this styleable and
252 // the package it comes from.
253 styleable_attr.field_name =
254 TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700255
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800256 // Look up the symbol so that we can write out in the comments what are possible legal values
257 // for this attribute.
258 const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700259 if (symbol && symbol->attribute) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800260 // Copy the symbol data structure because the returned instance can be destroyed.
261 styleable_attr.symbol = *symbol;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700262 }
263 sorted_attributes.push_back(std::move(styleable_attr));
264 }
265
266 // Sort the attributes by ID.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800267 std::sort(sorted_attributes.begin(), sorted_attributes.end());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700268
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800269 // Build the JavaDoc comment for the Styleable array. This has references to child attributes
270 // and what possible values can be used for them.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700271 const size_t attr_count = sorted_attributes.size();
272 if (attr_count > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 std::stringstream styleable_comment;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800274 if (!styleable.GetComment().empty()) {
275 styleable_comment << styleable.GetComment() << "\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800276 } else {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800277 // Apply a default intro comment if the styleable has no comments of its own.
278 styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n";
Adam Lesinski74605cd2016-03-03 15:39:50 -0800279 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700280
281 styleable_comment << "<p>Includes the following attributes:</p>\n"
282 "<table>\n"
283 "<colgroup align=\"left\" />\n"
284 "<colgroup align=\"left\" />\n"
285 "<tr><th>Attribute</th><th>Description</th></tr>\n";
286
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800287 // Build the table of attributes with their links and names.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700288 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800289 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700290 continue;
291 }
292
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800293 StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700294 if (attr_comment_line.contains("@removed")) {
295 // Removed attributes are public but hidden from the documentation, so
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800296 // don't emit them as part of the class documentation.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700297 continue;
298 }
299
300 const ResourceName& attr_name = entry.attr_ref->name.value();
301 styleable_comment << "<tr><td>";
302 styleable_comment << "<code>{@link #" << entry.field_name << " "
303 << (!attr_name.package.empty()
304 ? attr_name.package
305 : context_->GetCompilationPackage())
306 << ":" << attr_name.entry << "}</code>";
307 styleable_comment << "</td>";
308
309 styleable_comment << "<td>";
310
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800311 // Only use the comment up until the first '.'. This is to stay compatible with
312 // the way old AAPT did it (presumably to keep it short and to avoid including
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700313 // annotations like @hide which would affect this Styleable).
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800314 auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.');
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700315 if (iter != attr_comment_line.end()) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800316 attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700317 }
318 styleable_comment << attr_comment_line << "</td></tr>\n";
319 }
320 styleable_comment << "</table>\n";
321
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800322 // Generate the @see lines for each attribute.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 for (const StyleableAttr& entry : sorted_attributes) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800324 if (SkipSymbol(entry.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700325 continue;
326 }
327 styleable_comment << "@see #" << entry.field_name << "\n";
328 }
329
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800330 array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700331 }
332
Adam Lesinski418763f2017-04-11 17:36:53 -0700333 if (out_r_txt != nullptr) {
334 *out_r_txt << "int[] styleable " << array_field_name << " {";
335 }
336
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700337 // Add the ResourceIds to the array member.
Adam Lesinski418763f2017-04-11 17:36:53 -0700338 for (size_t i = 0; i < attr_count; i++) {
339 const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800340 array_def->AddElement(id);
Adam Lesinski418763f2017-04-11 17:36:53 -0700341
342 if (out_r_txt != nullptr) {
343 if (i != 0) {
344 *out_r_txt << ",";
345 }
346 *out_r_txt << " " << id;
347 }
348 }
349
350 if (out_r_txt != nullptr) {
351 *out_r_txt << " }\n";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700352 }
353
354 // Add the Styleable array to the Styleable class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800355 out_class_def->AddMember(std::move(array_def));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700356
357 // Now we emit the indices into the array.
358 for (size_t i = 0; i < attr_count; i++) {
359 const StyleableAttr& styleable_attr = sorted_attributes[i];
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800360 if (SkipSymbol(styleable_attr.symbol)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700361 continue;
362 }
363
364 StringPiece comment = styleable_attr.attr_ref->GetComment();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800365 if (styleable_attr.symbol.value().attribute && comment.empty()) {
366 comment = styleable_attr.symbol.value().attribute->GetComment();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700367 }
368
369 if (comment.contains("@removed")) {
370 // Removed attributes are public but hidden from the documentation, so
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800371 // don't emit them as part of the class documentation.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700372 continue;
373 }
374
375 const ResourceName& attr_name = styleable_attr.attr_ref->name.value();
376
377 StringPiece package_name = attr_name.package;
378 if (package_name.empty()) {
379 package_name = context_->GetCompilationPackage();
380 }
381
382 std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>(
383 sorted_attributes[i].field_name, static_cast<uint32_t>(i));
384
385 AnnotationProcessor* attr_processor = index_member->GetCommentBuilder();
386
387 if (!comment.empty()) {
388 attr_processor->AppendComment("<p>\n@attr description");
389 attr_processor->AppendComment(comment);
390 } else {
391 std::stringstream default_comment;
392 default_comment << "<p>This symbol is the offset where the "
393 << "{@link " << package_name << ".R.attr#"
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800394 << TransformToFieldName(attr_name.entry) << "}\n"
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700395 << "attribute's value can be found in the "
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800396 << "{@link #" << array_field_name << "} array.";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700397 attr_processor->AppendComment(default_comment.str());
398 }
399
400 attr_processor->AppendNewLine();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800401 AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700402 attr_processor->AppendNewLine();
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800403 attr_processor->AppendComment(
404 StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700405
Adam Lesinski418763f2017-04-11 17:36:53 -0700406 if (out_r_txt != nullptr) {
407 *out_r_txt << StringPrintf("int styleable %s %d\n", sorted_attributes[i].field_name.data(),
408 (int)i);
409 }
410
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800411 out_class_def->AddMember(std::move(index_member));
412 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700413
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800414 // If there is a rewrite method to generate, add the statements that rewrite package IDs
415 // for this styleable.
416 if (out_rewrite_method != nullptr) {
417 out_rewrite_method->AppendStatement(
418 StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
419 out_rewrite_method->AppendStatement(
420 StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
421 out_rewrite_method->AppendStatement(
422 StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
423 array_field_name.data(), array_field_name.data()));
424 out_rewrite_method->AppendStatement(" }");
425 out_rewrite_method->AppendStatement("}");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700426 }
Adam Lesinski74605cd2016-03-03 15:39:50 -0800427}
428
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800429void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
430 const ResourceEntry& entry, ClassDefinition* out_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700431 MethodDefinition* out_rewrite_method,
432 std::ostream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800433 const std::string field_name = TransformToFieldName(name.entry);
434 std::unique_ptr<ResourceMember> resource_member =
435 util::make_unique<ResourceMember>(field_name, id);
436
437 // Build the comments and annotations for this entry.
438 AnnotationProcessor* processor = resource_member->GetCommentBuilder();
439
440 // Add the comments from any <public> tags.
441 if (entry.symbol_status.state != SymbolState::kUndefined) {
442 processor->AppendComment(entry.symbol_status.comment);
443 }
444
445 // Add the comments from all configurations of this entry.
446 for (const auto& config_value : entry.values) {
447 processor->AppendComment(config_value->value->GetComment());
448 }
449
450 // If this is an Attribute, append the format Javadoc.
451 if (!entry.values.empty()) {
452 if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) {
453 // We list out the available values for the given attribute.
454 AddAttributeFormatDoc(processor, attr);
455 }
456 }
457
458 out_class_def->AddMember(std::move(resource_member));
459
Adam Lesinski418763f2017-04-11 17:36:53 -0700460 if (out_r_txt != nullptr) {
461 *out_r_txt << "int " << name.type << " " << field_name << " " << id << "\n";
462 }
463
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800464 if (out_rewrite_method != nullptr) {
465 const StringPiece& type_str = ToString(name.type);
466 out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
467 type_str.data(), field_name.data(),
468 type_str.data(), field_name.data()));
469 }
470}
471
472Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
473 const StringPiece& package_name_to_generate,
474 const ResourceEntry& entry) {
475 if (SkipSymbol(entry.symbol_status.state)) {
476 return {};
477 }
478
479 std::string unmangled_package;
480 std::string unmangled_name = entry.name;
481 if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) {
482 // The entry name was mangled, and we successfully unmangled it.
483 // Check that we want to emit this symbol.
484 if (package_name != unmangled_package) {
485 // Skip the entry if it doesn't belong to the package we're writing.
486 return {};
487 }
488 } else if (package_name_to_generate != package_name) {
489 // We are processing a mangled package name,
490 // but this is a non-mangled resource.
491 return {};
492 }
493 return {std::move(unmangled_name)};
494}
495
496bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
497 const ResourceTablePackage& package,
498 const ResourceTableType& type,
499 ClassDefinition* out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700500 MethodDefinition* out_rewrite_method_def,
501 std::ostream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800502 for (const auto& entry : type.entries) {
503 const Maybe<std::string> unmangled_name =
504 UnmangleResource(package.name, package_name_to_generate, *entry);
505 if (!unmangled_name) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700506 continue;
Adam Lesinski74605cd2016-03-03 15:39:50 -0800507 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700508
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800509 // Create an ID if there is one (static libraries don't need one).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700510 ResourceId id;
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800511 if (package.id && type.id && entry->id) {
512 id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
Adam Lesinski74605cd2016-03-03 15:39:50 -0800513 }
514
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800515 // We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
516 const ResourceNameRef resource_name(
517 package_name_to_generate,
518 type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type,
519 unmangled_name.value());
Adam Lesinskib274e352015-11-06 15:14:35 -0800520
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800521 // Check to see if the unmangled name is a valid Java name (not a keyword).
522 if (!IsValidSymbol(unmangled_name.value())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700523 std::stringstream err;
524 err << "invalid symbol name '" << resource_name << "'";
525 error_ = err.str();
526 return false;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700527 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700528
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800529 if (resource_name.type == ResourceType::kStyleable) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700530 CHECK(!entry->values.empty());
531
532 const Styleable* styleable =
533 static_cast<const Styleable*>(entry->values.front()->value.get());
534
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800535 ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
Adam Lesinski418763f2017-04-11 17:36:53 -0700536 out_rewrite_method_def, out_r_txt);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700537 } else {
Adam Lesinski418763f2017-04-11 17:36:53 -0700538 ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
539 out_r_txt);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540 }
541 }
542 return true;
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700543}
544
Adam Lesinski418763f2017-04-11 17:36:53 -0700545bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out,
546 std::ostream* out_r_txt) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700547 return Generate(package_name_to_generate, package_name_to_generate, out);
Adam Lesinski769de982015-04-10 19:43:55 -0700548}
549
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800550static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
551 AnnotationProcessor* processor) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700552 for (const std::string& annotation : annotations) {
553 std::string proper_annotation = "@";
554 proper_annotation += annotation;
555 processor->AppendComment(proper_annotation);
556 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700557}
558
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700559bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
Adam Lesinski418763f2017-04-11 17:36:53 -0700560 const StringPiece& out_package_name, std::ostream* out,
561 std::ostream* out_r_txt) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800562 ClassDefinition r_class("R", ClassQualifier::kNone, true);
563 std::unique_ptr<MethodDefinition> rewrite_method;
564
565 // Generate an onResourcesLoaded() callback if requested.
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800566 if (options_.rewrite_callback_options) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800567 rewrite_method =
568 util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)");
Adam Lesinskib5dc4bd2017-02-22 19:29:29 -0800569 for (const std::string& package_to_callback :
570 options_.rewrite_callback_options.value().packages_to_callback) {
571 rewrite_method->AppendStatement(
572 StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
573 }
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800574 }
Adam Lesinski3524a232016-04-01 19:19:24 -0700575
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700576 for (const auto& package : table_->packages) {
577 for (const auto& type : package->types) {
578 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800579 // We generate these as part of the kAttr type, so skip them here.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700580 continue;
581 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800582
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800583 // Stay consistent with AAPT and generate an empty type class if the R class
584 // is public.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700585 const bool force_creation_if_empty =
586 (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800587
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800588 std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
589 ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty);
590 if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
Adam Lesinski418763f2017-04-11 17:36:53 -0700591 rewrite_method.get(), out_r_txt)) {
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700592 return false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700593 }
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700594
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700595 if (type->type == ResourceType::kAttr) {
596 // Also include private attributes in this same class.
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800597 const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700598 if (priv_type) {
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800599 if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
Adam Lesinski418763f2017-04-11 17:36:53 -0700600 rewrite_method.get(), out_r_txt)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700601 return false;
602 }
603 }
604 }
605
606 if (type->type == ResourceType::kStyleable &&
607 options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
608 // When generating a public R class, we don't want Styleable to be part
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800609 // of the API. It is only emitted for documentation purposes.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700610 class_def->GetCommentBuilder()->AppendComment("@doconly");
611 }
612
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800613 AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700614
615 r_class.AddMember(std::move(class_def));
616 }
617 }
618
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800619 if (rewrite_method != nullptr) {
620 r_class.AddMember(std::move(rewrite_method));
621 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700622
Adam Lesinskiceb9b2f2017-02-16 12:05:42 -0800623 AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
624
625 if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700626 return false;
627 }
628
629 out->flush();
Adam Lesinski418763f2017-04-11 17:36:53 -0700630
631 if (out_r_txt != nullptr) {
632 out_r_txt->flush();
633
634 if (!*out_r_txt) {
635 error_ = android::base::SystemErrorCodeToString(errno);
636 return false;
637 }
638 }
639
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700640 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800641}
642
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700643} // namespace aapt