blob: 7280f3a968a047dbcb1b66aa30e02d7b5dee3d1f [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 Lesinski769de982015-04-10 19:43:55 -070017#include "NameMangler.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080018#include "Resource.h"
19#include "ResourceTable.h"
20#include "ResourceValues.h"
Adam Lesinski3b4cd942015-10-30 16:31:42 -070021#include "ValueVisitor.h"
22
Adam Lesinskica5638f2015-10-21 14:42:43 -070023#include "java/AnnotationProcessor.h"
Adam Lesinskib274e352015-11-06 15:14:35 -080024#include "java/ClassDefinitionWriter.h"
Adam Lesinskica5638f2015-10-21 14:42:43 -070025#include "java/JavaClassGenerator.h"
Adam Lesinskib274e352015-11-06 15:14:35 -080026#include "util/Comparators.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070027#include "util/StringPiece.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080028
Adam Lesinskica2fc352015-04-03 12:08:26 -070029#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080030#include <ostream>
31#include <set>
32#include <sstream>
33#include <tuple>
34
35namespace aapt {
36
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080038 mTable(table), mOptions(options) {
39}
40
Adam Lesinski1ab598f2015-08-14 14:26:04 -070041static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
42 *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
43 " *\n"
44 " * This class was automatically generated by the\n"
45 " * aapt tool from the resource data it found. It\n"
46 " * should not be modified by hand.\n"
47 " */\n\n"
48 "package " << packageNameToGenerate << ";\n\n";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080049}
50
51static const std::set<StringPiece16> sJavaIdentifiers = {
52 u"abstract", u"assert", u"boolean", u"break", u"byte",
53 u"case", u"catch", u"char", u"class", u"const", u"continue",
54 u"default", u"do", u"double", u"else", u"enum", u"extends",
55 u"final", u"finally", u"float", u"for", u"goto", u"if",
56 u"implements", u"import", u"instanceof", u"int", u"interface",
57 u"long", u"native", u"new", u"package", u"private", u"protected",
58 u"public", u"return", u"short", u"static", u"strictfp", u"super",
59 u"switch", u"synchronized", u"this", u"throw", u"throws",
60 u"transient", u"try", u"void", u"volatile", u"while", u"true",
61 u"false", u"null"
62};
63
64static bool isValidSymbol(const StringPiece16& symbol) {
65 return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
66}
67
68/*
69 * Java symbols can not contain . or -, but those are valid in a resource name.
70 * Replace those with '_'.
71 */
72static std::u16string transform(const StringPiece16& symbol) {
73 std::u16string output = symbol.toString();
74 for (char16_t& c : output) {
75 if (c == u'.' || c == u'-') {
76 c = u'_';
77 }
78 }
79 return output;
80}
81
Adam Lesinski9e10ac72015-10-16 14:37:48 -070082bool JavaClassGenerator::skipSymbol(SymbolState state) {
83 switch (mOptions.types) {
84 case JavaClassGeneratorOptions::SymbolTypes::kAll:
85 return false;
86 case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
87 return state == SymbolState::kUndefined;
88 case JavaClassGeneratorOptions::SymbolTypes::kPublic:
89 return state != SymbolState::kPublic;
90 }
91 return true;
92}
93
Adam Lesinskib274e352015-11-06 15:14:35 -080094void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef,
95 AnnotationProcessor* processor,
96 const StringPiece16& packageNameToGenerate,
97 const std::u16string& entryName,
98 const Styleable* styleable) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080099 // This must be sorted by resource ID.
Adam Lesinski838a6872015-05-01 13:14:05 -0700100 std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700101 sortedAttributes.reserve(styleable->entries.size());
102 for (const auto& attr : styleable->entries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700103 // If we are not encoding final attributes, the styleable entry may have no ID
104 // if we are building a static library.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
106 assert(attr.name && "no name set for Styleable entry");
107 sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800108 }
109 std::sort(sortedAttributes.begin(), sortedAttributes.end());
110
Adam Lesinskib274e352015-11-06 15:14:35 -0800111 auto accessorFunc = [](const std::pair<ResourceId, ResourceNameRef>& a) -> ResourceId {
112 return a.first;
113 };
114
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800115 // First we emit the array containing the IDs of each attribute.
Adam Lesinskib274e352015-11-06 15:14:35 -0800116 outClassDef->addArrayMember(transform(entryName), processor,
117 sortedAttributes.begin(),
118 sortedAttributes.end(),
119 accessorFunc);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800120
121 // Now we emit the indices into the array.
Adam Lesinskib274e352015-11-06 15:14:35 -0800122 size_t attrCount = sortedAttributes.size();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800123 for (size_t i = 0; i < attrCount; i++) {
Adam Lesinskib274e352015-11-06 15:14:35 -0800124 std::stringstream name;
125 name << transform(entryName);
Adam Lesinski838a6872015-05-01 13:14:05 -0700126
127 // We may reference IDs from other packages, so prefix the entry name with
128 // the package.
129 const ResourceNameRef& itemName = sortedAttributes[i].second;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700130 if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
Adam Lesinskib274e352015-11-06 15:14:35 -0800131 name << "_" << transform(itemName.package);
Adam Lesinski838a6872015-05-01 13:14:05 -0700132 }
Adam Lesinskib274e352015-11-06 15:14:35 -0800133 name << "_" << transform(itemName.entry);
134
135 outClassDef->addIntMember(name.str(), nullptr, i);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800136 }
137}
138
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700139static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
140 const uint32_t typeMask = attr->typeMask;
141 if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
142 processor->appendComment(
143 "<p>May be a reference to another resource, in the form\n"
144 "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
145 "attribute in the form\n"
146 "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
147 }
148
149 if (typeMask & android::ResTable_map::TYPE_STRING) {
150 processor->appendComment(
Adam Lesinskib274e352015-11-06 15:14:35 -0800151 "<p>May be a string value, using '\\\\;' to escape characters such as\n"
152 "'\\\\n' or '\\\\uxxxx' for a unicode character;");
Adam Lesinski3b4cd942015-10-30 16:31:42 -0700153 }
154
155 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
156 processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
157 }
158
159 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
160 processor->appendComment(
161 "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
162 "\"<code>false</code>\".");
163 }
164
165 if (typeMask & android::ResTable_map::TYPE_COLOR) {
166 processor->appendComment(
167 "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
168 "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
169 "\"<code>#<i>aarrggbb</i></code>\".");
170 }
171
172 if (typeMask & android::ResTable_map::TYPE_FLOAT) {
173 processor->appendComment(
174 "<p>May be a floating point value, such as \"<code>1.2</code>\".");
175 }
176
177 if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
178 processor->appendComment(
179 "<p>May be a dimension value, which is a floating point number appended with a\n"
180 "unit such as \"<code>14.5sp</code>\".\n"
181 "Available units are: px (pixels), dp (density-independent pixels),\n"
182 "sp (scaled pixels based on preferred font size), in (inches), and\n"
183 "mm (millimeters).");
184 }
185
186 if (typeMask & android::ResTable_map::TYPE_FRACTION) {
187 processor->appendComment(
188 "<p>May be a fractional value, which is a floating point number appended with\n"
189 "either % or %p, such as \"<code>14.5%</code>\".\n"
190 "The % suffix always means a percentage of the base size;\n"
191 "the optional %p suffix provides a size relative to some parent container.");
192 }
193
194 if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
195 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
196 processor->appendComment(
197 "<p>Must be one or more (separated by '|') of the following "
198 "constant values.</p>");
199 } else {
200 processor->appendComment("<p>Must be one of the following constant values.</p>");
201 }
202
203 processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
204 "<colgroup align=\"left\" />\n"
205 "<colgroup align=\"left\" />\n"
206 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
207 for (const Attribute::Symbol& symbol : attr->symbols) {
208 std::stringstream line;
209 line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
210 << "<td>" << std::hex << symbol.value << std::dec << "</td>"
211 << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
212 processor->appendComment(line.str());
213 }
214 processor->appendComment("</table>");
215 }
216}
217
Adam Lesinskib274e352015-11-06 15:14:35 -0800218bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef,
219 const StringPiece16& packageNameToGenerate,
220 const ResourceTablePackage* package,
221 const ResourceTableType* type) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700222 for (const auto& entry : type->entries) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700223 if (skipSymbol(entry->symbolStatus.state)) {
224 continue;
225 }
226
227 ResourceId id(package->id.value(), type->id.value(), entry->id.value());
Adam Lesinski769de982015-04-10 19:43:55 -0700228 assert(id.isValid());
229
Adam Lesinskib274e352015-11-06 15:14:35 -0800230 std::u16string unmangledPackage;
231 std::u16string unmangledName = entry->name;
Adam Lesinski769de982015-04-10 19:43:55 -0700232 if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
233 // The entry name was mangled, and we successfully unmangled it.
234 // Check that we want to emit this symbol.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700235 if (package->name != unmangledPackage) {
Adam Lesinski769de982015-04-10 19:43:55 -0700236 // Skip the entry if it doesn't belong to the package we're writing.
237 continue;
238 }
Adam Lesinskib274e352015-11-06 15:14:35 -0800239 } else if (packageNameToGenerate != package->name) {
240 // We are processing a mangled package name,
241 // but this is a non-mangled resource.
242 continue;
Adam Lesinski769de982015-04-10 19:43:55 -0700243 }
244
245 if (!isValidSymbol(unmangledName)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700246 ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
Adam Lesinski769de982015-04-10 19:43:55 -0700247 std::stringstream err;
248 err << "invalid symbol name '" << resourceName << "'";
249 mError = err.str();
250 return false;
251 }
252
Adam Lesinskib274e352015-11-06 15:14:35 -0800253 // Build the comments and annotations for this entry.
254
255 AnnotationProcessor processor;
256 if (entry->symbolStatus.state != SymbolState::kUndefined) {
257 processor.appendComment(entry->symbolStatus.comment);
258 }
259
260 for (const auto& configValue : entry->values) {
261 processor.appendComment(configValue.value->getComment());
262 }
263
264 // If this is an Attribute, append the format Javadoc.
265 if (!entry->values.empty()) {
266 if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
267 // We list out the available values for the given attribute.
268 addAttributeFormatDoc(&processor, attr);
269 }
270 }
271
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700272 if (type->type == ResourceType::kStyleable) {
Adam Lesinski769de982015-04-10 19:43:55 -0700273 assert(!entry->values.empty());
Adam Lesinskib274e352015-11-06 15:14:35 -0800274 const Styleable* styleable = static_cast<const Styleable*>(
275 entry->values.front().value.get());
276 writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate,
277 unmangledName, styleable);
Adam Lesinski769de982015-04-10 19:43:55 -0700278 } else {
Adam Lesinskib274e352015-11-06 15:14:35 -0800279 outClassDef->addResourceMember(transform(unmangledName), &processor, id);
Adam Lesinski769de982015-04-10 19:43:55 -0700280 }
281 }
282 return true;
283}
284
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700285bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700286 return generate(packageNameToGenerate, packageNameToGenerate, out);
287}
288
289bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
290 const StringPiece16& outPackageName, std::ostream* out) {
291 generateHeader(outPackageName, out);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800292
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700293 *out << "public final class R {\n";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800294
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700295 for (const auto& package : mTable->packages) {
296 for (const auto& type : package->types) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700297 if (type->type == ResourceType::kAttrPrivate) {
Adam Lesinskib274e352015-11-06 15:14:35 -0800298 continue;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700299 }
Adam Lesinskib274e352015-11-06 15:14:35 -0800300
301 ClassDefinitionWriterOptions classOptions;
302 classOptions.useFinalQualifier = mOptions.useFinal;
303 classOptions.forceCreationIfEmpty =
304 (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
305 ClassDefinitionWriter classDef(toString(type->type), classOptions);
306 bool result = writeEntriesForClass(&classDef, packageNameToGenerate,
307 package.get(), type.get());
308 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700309 return false;
310 }
Adam Lesinskib274e352015-11-06 15:14:35 -0800311
312 if (type->type == ResourceType::kAttr) {
313 // Also include private attributes in this same class.
314 auto iter = std::lower_bound(package->types.begin(), package->types.end(),
315 ResourceType::kAttrPrivate, cmp::lessThanType);
316 if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) {
317 result = writeEntriesForClass(&classDef, packageNameToGenerate,
318 package.get(), iter->get());
319 if (!result) {
320 return false;
321 }
322 }
323 }
324
325 AnnotationProcessor processor;
326 if (type->type == ResourceType::kStyleable &&
327 mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
328 // When generating a public R class, we don't want Styleable to be part of the API.
329 // It is only emitted for documentation purposes.
330 processor.appendComment("@doconly");
331 }
332 classDef.writeToStream(out, " ", &processor);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800333 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800334 }
335
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700336 *out << "}\n";
337 out->flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800338 return true;
339}
340
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700341
342
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800343} // namespace aapt