blob: ecc5cd2bdcfabf634ac8b68342281a3928d3860f [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
17#include "Resource.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "flatten/ResourceTypeExtensions.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080019#include "ResourceValues.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070020#include "util/Util.h"
21#include "ValueVisitor.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080022
23#include <androidfw/ResourceTypes.h>
24#include <limits>
25
26namespace aapt {
27
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028template <typename Derived>
29void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
30 visitor->visit(static_cast<Derived*>(this));
31}
32
33template <typename Derived>
34void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
35 visitor->visit(static_cast<Derived*>(this));
36}
37
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080038bool Value::isItem() const {
39 return false;
40}
41
42bool Value::isWeak() const {
43 return false;
44}
45
46bool Item::isItem() const {
47 return true;
48}
49
50RawString::RawString(const StringPool::Ref& ref) : value(ref) {
51}
52
Adam Lesinski769de982015-04-10 19:43:55 -070053RawString* RawString::clone(StringPool* newPool) const {
54 return new RawString(newPool->makeRef(*value));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055}
56
Adam Lesinski1ab598f2015-08-14 14:26:04 -070057bool RawString::flatten(android::Res_value* outValue) const {
58 outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
59 outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080060 return true;
61}
62
Adam Lesinski1ab598f2015-08-14 14:26:04 -070063void RawString::print(std::ostream* out) const {
64 *out << "(raw string) " << *value;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080065}
66
67Reference::Reference() : referenceType(Reference::Type::kResource) {
68}
69
70Reference::Reference(const ResourceNameRef& n, Type t) :
71 name(n.toResourceName()), referenceType(t) {
72}
73
74Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
75}
76
Adam Lesinski1ab598f2015-08-14 14:26:04 -070077bool Reference::flatten(android::Res_value* outValue) const {
78 outValue->dataType = (referenceType == Reference::Type::kResource)
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080079 ? android::Res_value::TYPE_REFERENCE
80 : android::Res_value::TYPE_ATTRIBUTE;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070081 outValue->data = util::hostToDevice32(id ? id.value().id : 0);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080082 return true;
83}
84
Adam Lesinski769de982015-04-10 19:43:55 -070085Reference* Reference::clone(StringPool* /*newPool*/) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080086 Reference* ref = new Reference();
87 ref->referenceType = referenceType;
88 ref->name = name;
89 ref->id = id;
90 return ref;
91}
92
Adam Lesinski1ab598f2015-08-14 14:26:04 -070093void Reference::print(std::ostream* out) const {
94 *out << "(reference) ";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080095 if (referenceType == Reference::Type::kResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070096 *out << "@";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080097 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070098 *out << "?";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080099 }
100
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700101 if (name) {
102 *out << name.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800103 }
104
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 if (id && !Res_INTERNALID(id.value().id)) {
106 *out << " " << id.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800107 }
108}
109
110bool Id::isWeak() const {
111 return true;
112}
113
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700114bool Id::flatten(android::Res_value* out) const {
115 out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
116 out->data = util::hostToDevice32(0);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800117 return true;
118}
119
Adam Lesinski769de982015-04-10 19:43:55 -0700120Id* Id::clone(StringPool* /*newPool*/) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800121 return new Id();
122}
123
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700124void Id::print(std::ostream* out) const {
125 *out << "(id)";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800126}
127
128String::String(const StringPool::Ref& ref) : value(ref) {
129}
130
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700131bool String::flatten(android::Res_value* outValue) const {
132 // Verify that our StringPool index is within encode-able limits.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800133 if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
134 return false;
135 }
136
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700137 outValue->dataType = android::Res_value::TYPE_STRING;
138 outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800139 return true;
140}
141
Adam Lesinski769de982015-04-10 19:43:55 -0700142String* String::clone(StringPool* newPool) const {
143 return new String(newPool->makeRef(*value));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800144}
145
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700146void String::print(std::ostream* out) const {
147 *out << "(string) \"" << *value << "\"";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800148}
149
150StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
151}
152
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700153bool StyledString::flatten(android::Res_value* outValue) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800154 if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
155 return false;
156 }
157
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700158 outValue->dataType = android::Res_value::TYPE_STRING;
159 outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800160 return true;
161}
162
Adam Lesinski769de982015-04-10 19:43:55 -0700163StyledString* StyledString::clone(StringPool* newPool) const {
164 return new StyledString(newPool->makeRef(value));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800165}
166
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700167void StyledString::print(std::ostream* out) const {
168 *out << "(styled string) \"" << *value->str << "\"";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800169}
170
171FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
172}
173
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700174bool FileReference::flatten(android::Res_value* outValue) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800175 if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
176 return false;
177 }
178
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700179 outValue->dataType = android::Res_value::TYPE_STRING;
180 outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800181 return true;
182}
183
Adam Lesinski769de982015-04-10 19:43:55 -0700184FileReference* FileReference::clone(StringPool* newPool) const {
185 return new FileReference(newPool->makeRef(*path));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800186}
187
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700188void FileReference::print(std::ostream* out) const {
189 *out << "(file) " << *path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800190}
191
192BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
193}
194
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700195BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
196 value.dataType = dataType;
197 value.data = data;
198}
199
200bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
201 outValue->dataType = value.dataType;
202 outValue->data = util::hostToDevice32(value.data);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800203 return true;
204}
205
Adam Lesinski769de982015-04-10 19:43:55 -0700206BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800207 return new BinaryPrimitive(value);
208}
209
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700210void BinaryPrimitive::print(std::ostream* out) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800211 switch (value.dataType) {
212 case android::Res_value::TYPE_NULL:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700213 *out << "(null)";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800214 break;
215 case android::Res_value::TYPE_INT_DEC:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700216 *out << "(integer) " << value.data;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800217 break;
218 case android::Res_value::TYPE_INT_HEX:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700219 *out << "(integer) " << std::hex << value.data << std::dec;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800220 break;
221 case android::Res_value::TYPE_INT_BOOLEAN:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700222 *out << "(boolean) " << (value.data != 0 ? "true" : "false");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800223 break;
224 case android::Res_value::TYPE_INT_COLOR_ARGB8:
225 case android::Res_value::TYPE_INT_COLOR_RGB8:
226 case android::Res_value::TYPE_INT_COLOR_ARGB4:
227 case android::Res_value::TYPE_INT_COLOR_RGB4:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700228 *out << "(color) #" << std::hex << value.data << std::dec;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800229 break;
230 default:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700231 *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
232 << std::hex << value.data << std::dec;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800233 break;
234 }
235}
236
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800237Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
238}
239
240bool Attribute::isWeak() const {
241 return weak;
242}
243
Adam Lesinski769de982015-04-10 19:43:55 -0700244Attribute* Attribute::clone(StringPool* /*newPool*/) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800245 Attribute* attr = new Attribute(weak);
246 attr->typeMask = typeMask;
247 std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
248 return attr;
249}
250
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700251void Attribute::printMask(std::ostream* out) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800252 if (typeMask == android::ResTable_map::TYPE_ANY) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700253 *out << "any";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800254 return;
255 }
256
257 bool set = false;
258 if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
259 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800260 set = true;
261 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700262 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800263 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700264 *out << "reference";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800265 }
266
267 if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
268 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800269 set = true;
270 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700271 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800272 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700273 *out << "string";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800274 }
275
276 if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
277 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800278 set = true;
279 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700280 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800281 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700282 *out << "integer";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800283 }
284
285 if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
286 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800287 set = true;
288 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700289 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800290 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700291 *out << "boolean";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800292 }
293
294 if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
295 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800296 set = true;
297 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700298 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800299 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700300 *out << "color";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800301 }
302
303 if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
304 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800305 set = true;
306 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700307 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800308 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700309 *out << "float";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800310 }
311
312 if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
313 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800314 set = true;
315 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700316 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800317 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700318 *out << "dimension";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800319 }
320
321 if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
322 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800323 set = true;
324 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700325 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800326 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700327 *out << "fraction";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800328 }
329
330 if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
331 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800332 set = true;
333 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700334 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800335 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700336 *out << "enum";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800337 }
338
339 if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
340 if (!set) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800341 set = true;
342 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700343 *out << "|";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800344 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700345 *out << "flags";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800346 }
Adam Lesinski330edcd2015-05-04 17:40:56 -0700347}
348
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700349void Attribute::print(std::ostream* out) const {
350 *out << "(attr) ";
Adam Lesinski330edcd2015-05-04 17:40:56 -0700351 printMask(out);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800352
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700353 if (!symbols.empty()) {
354 *out << " ["
355 << util::joiner(symbols.begin(), symbols.end(), ", ")
356 << "]";
357 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800358
359 if (weak) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700360 *out << " [weak]";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800361 }
362}
363
Adam Lesinski769de982015-04-10 19:43:55 -0700364Style* Style::clone(StringPool* newPool) const {
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700365 Style* style = new Style();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800366 style->parent = parent;
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700367 style->parentInferred = parentInferred;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800368 for (auto& entry : entries) {
369 style->entries.push_back(Entry{
370 entry.key,
Adam Lesinski769de982015-04-10 19:43:55 -0700371 std::unique_ptr<Item>(entry.value->clone(newPool))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800372 });
373 }
374 return style;
375}
376
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700377void Style::print(std::ostream* out) const {
378 *out << "(style) ";
379 if (parent && parent.value().name) {
380 *out << parent.value().name.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800381 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700382 *out << " ["
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800383 << util::joiner(entries.begin(), entries.end(), ", ")
384 << "]";
385}
386
387static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700388 if (value.key.name) {
389 out << value.key.name.value();
390 } else {
391 out << "???";
392 }
393 out << " = ";
394 value.value->print(&out);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800395 return out;
396}
397
Adam Lesinski769de982015-04-10 19:43:55 -0700398Array* Array::clone(StringPool* newPool) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800399 Array* array = new Array();
400 for (auto& item : items) {
Adam Lesinski769de982015-04-10 19:43:55 -0700401 array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800402 }
403 return array;
404}
405
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700406void Array::print(std::ostream* out) const {
407 *out << "(array) ["
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800408 << util::joiner(items.begin(), items.end(), ", ")
409 << "]";
410}
411
Adam Lesinski769de982015-04-10 19:43:55 -0700412Plural* Plural::clone(StringPool* newPool) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800413 Plural* p = new Plural();
414 const size_t count = values.size();
415 for (size_t i = 0; i < count; i++) {
416 if (values[i]) {
Adam Lesinski769de982015-04-10 19:43:55 -0700417 p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800418 }
419 }
420 return p;
421}
422
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700423void Plural::print(std::ostream* out) const {
424 *out << "(plural)";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800425}
426
427static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
428 return out << *item;
429}
430
Adam Lesinski769de982015-04-10 19:43:55 -0700431Styleable* Styleable::clone(StringPool* /*newPool*/) const {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800432 Styleable* styleable = new Styleable();
433 std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
434 return styleable;
435}
436
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437void Styleable::print(std::ostream* out) const {
438 *out << "(styleable) " << " ["
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800439 << util::joiner(entries.begin(), entries.end(), ", ")
440 << "]";
441}
442
443} // namespace aapt