blob: e3ec0aefa2d44b58c38ec2ffab6d6d43b848649a [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
Adam Lesinski282e1812014-01-23 18:17:42 -08006#include "StringPool.h"
Adam Lesinski282e1812014-01-23 18:17:42 -08007
8#include <utils/ByteOrder.h>
9#include <utils/SortedVector.h>
Dan Albert0de19ad2014-10-01 11:34:17 -070010
11#include <algorithm>
12
13#include "ResourceTable.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080014
Andreas Gampe2412f842014-09-30 20:55:57 -070015// SSIZE: mingw does not have signed size_t == ssize_t.
Adam Lesinski282e1812014-01-23 18:17:42 -080016#if HAVE_PRINTF_ZD
17# define ZD "%zd"
18# define ZD_TYPE ssize_t
Andreas Gampe2412f842014-09-30 20:55:57 -070019# define SSIZE(x) x
Adam Lesinski282e1812014-01-23 18:17:42 -080020#else
21# define ZD "%ld"
22# define ZD_TYPE long
Andreas Gampe2412f842014-09-30 20:55:57 -070023# define SSIZE(x) (signed size_t)x
Adam Lesinski282e1812014-01-23 18:17:42 -080024#endif
25
Andreas Gampe2412f842014-09-30 20:55:57 -070026// Set to true for noisy debug output.
27static const bool kIsDebug = false;
Adam Lesinski282e1812014-01-23 18:17:42 -080028
Dan Albertf348c152014-09-08 18:28:00 -070029void strcpy16_htod(char16_t* dst, const char16_t* src)
Adam Lesinski282e1812014-01-23 18:17:42 -080030{
31 while (*src) {
32 char16_t s = htods(*src);
33 *dst++ = s;
34 src++;
35 }
36 *dst = 0;
37}
38
39void printStringPool(const ResStringPool* pool)
40{
Adam Lesinski25e9d552014-05-19 15:01:43 -070041 if (pool->getError() == NO_INIT) {
42 printf("String pool is unitialized.\n");
43 return;
44 } else if (pool->getError() != NO_ERROR) {
45 printf("String pool is corrupt/invalid.\n");
46 return;
47 }
48
Adam Lesinski282e1812014-01-23 18:17:42 -080049 SortedVector<const void*> uniqueStrings;
50 const size_t N = pool->size();
51 for (size_t i=0; i<N; i++) {
52 size_t len;
53 if (pool->isUTF8()) {
54 uniqueStrings.add(pool->string8At(i, &len));
55 } else {
56 uniqueStrings.add(pool->stringAt(i, &len));
57 }
58 }
59
60 printf("String pool of " ZD " unique %s %s strings, " ZD " entries and "
61 ZD " styles using " ZD " bytes:\n",
62 (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16",
63 pool->isSorted() ? "sorted" : "non-sorted",
64 (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes());
65
66 const size_t NS = pool->size();
67 for (size_t s=0; s<NS; s++) {
68 String8 str = pool->string8ObjectAt(s);
69 printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string());
70 }
71}
72
73String8 StringPool::entry::makeConfigsString() const {
74 String8 configStr(configTypeName);
75 if (configStr.size() > 0) configStr.append(" ");
76 if (configs.size() > 0) {
77 for (size_t j=0; j<configs.size(); j++) {
78 if (j > 0) configStr.append(", ");
79 configStr.append(configs[j].toString());
80 }
81 } else {
82 configStr = "(none)";
83 }
84 return configStr;
85}
86
87int StringPool::entry::compare(const entry& o) const {
88 // Strings with styles go first, to reduce the size of the styles array.
89 // We don't care about the relative order of these strings.
90 if (hasStyles) {
91 return o.hasStyles ? 0 : -1;
92 }
93 if (o.hasStyles) {
94 return 1;
95 }
96
97 // Sort unstyled strings by type, then by logical configuration.
98 int comp = configTypeName.compare(o.configTypeName);
99 if (comp != 0) {
100 return comp;
101 }
102 const size_t LHN = configs.size();
103 const size_t RHN = o.configs.size();
104 size_t i=0;
105 while (i < LHN && i < RHN) {
106 comp = configs[i].compareLogical(o.configs[i]);
107 if (comp != 0) {
108 return comp;
109 }
110 i++;
111 }
112 if (LHN < RHN) return -1;
113 else if (LHN > RHN) return 1;
114 return 0;
115}
116
117StringPool::StringPool(bool utf8) :
118 mUTF8(utf8), mValues(-1)
119{
120}
121
122ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans,
123 const String8* configTypeName, const ResTable_config* config)
124{
125 ssize_t res = add(value, false, configTypeName, config);
126 if (res >= 0) {
127 addStyleSpans(res, spans);
128 }
129 return res;
130}
131
132ssize_t StringPool::add(const String16& value,
133 bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config)
134{
135 ssize_t vidx = mValues.indexOfKey(value);
136 ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1;
137 ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1;
138 if (eidx < 0) {
139 eidx = mEntries.add(entry(value));
140 if (eidx < 0) {
141 fprintf(stderr, "Failure adding string %s\n", String8(value).string());
142 return eidx;
143 }
144 }
145
146 if (configTypeName != NULL) {
147 entry& ent = mEntries.editItemAt(eidx);
Andreas Gampe2412f842014-09-30 20:55:57 -0700148 if (kIsDebug) {
149 printf("*** adding config type name %s, was %s\n",
150 configTypeName->string(), ent.configTypeName.string());
151 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800152 if (ent.configTypeName.size() <= 0) {
153 ent.configTypeName = *configTypeName;
154 } else if (ent.configTypeName != *configTypeName) {
155 ent.configTypeName = " ";
156 }
157 }
158
159 if (config != NULL) {
160 // Add this to the set of configs associated with the string.
161 entry& ent = mEntries.editItemAt(eidx);
162 size_t addPos;
163 for (addPos=0; addPos<ent.configs.size(); addPos++) {
164 int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
165 if (cmp >= 0) {
166 if (cmp > 0) {
Andreas Gampe2412f842014-09-30 20:55:57 -0700167 if (kIsDebug) {
168 printf("*** inserting config: %s\n", config->toString().string());
169 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800170 ent.configs.insertAt(*config, addPos);
171 }
172 break;
173 }
174 }
175 if (addPos >= ent.configs.size()) {
Andreas Gampe2412f842014-09-30 20:55:57 -0700176 if (kIsDebug) {
177 printf("*** adding config: %s\n", config->toString().string());
178 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800179 ent.configs.add(*config);
180 }
181 }
182
183 const bool first = vidx < 0;
184 const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ?
185 mEntryStyleArray[pos].spans.size() : 0;
186 if (first || styled || !mergeDuplicates) {
187 pos = mEntryArray.add(eidx);
188 if (first) {
189 vidx = mValues.add(value, pos);
190 }
191 entry& ent = mEntries.editItemAt(eidx);
192 ent.indices.add(pos);
193 }
194
Andreas Gampe2412f842014-09-30 20:55:57 -0700195 if (kIsDebug) {
196 printf("Adding string %s to pool: pos=%zd eidx=%zd vidx=%zd\n",
197 String8(value).string(), SSIZE(pos), SSIZE(eidx), SSIZE(vidx));
198 }
199
Adam Lesinski282e1812014-01-23 18:17:42 -0800200 return pos;
201}
202
203status_t StringPool::addStyleSpan(size_t idx, const String16& name,
204 uint32_t start, uint32_t end)
205{
206 entry_style_span span;
207 span.name = name;
208 span.span.firstChar = start;
209 span.span.lastChar = end;
210 return addStyleSpan(idx, span);
211}
212
213status_t StringPool::addStyleSpans(size_t idx, const Vector<entry_style_span>& spans)
214{
215 const size_t N=spans.size();
216 for (size_t i=0; i<N; i++) {
217 status_t err = addStyleSpan(idx, spans[i]);
218 if (err != NO_ERROR) {
219 return err;
220 }
221 }
222 return NO_ERROR;
223}
224
225status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span)
226{
227 // Place blank entries in the span array up to this index.
228 while (mEntryStyleArray.size() <= idx) {
229 mEntryStyleArray.add();
230 }
231
232 entry_style& style = mEntryStyleArray.editItemAt(idx);
233 style.spans.add(span);
234 mEntries.editItemAt(mEntryArray[idx]).hasStyles = true;
235 return NO_ERROR;
236}
237
Dan Albert0de19ad2014-10-01 11:34:17 -0700238StringPool::ConfigSorter::ConfigSorter(const StringPool& pool) : pool(pool)
Adam Lesinski282e1812014-01-23 18:17:42 -0800239{
Dan Albert0de19ad2014-10-01 11:34:17 -0700240}
241
242bool StringPool::ConfigSorter::operator()(size_t l, size_t r)
243{
244 const StringPool::entry& lhe = pool.mEntries[pool.mEntryArray[l]];
245 const StringPool::entry& rhe = pool.mEntries[pool.mEntryArray[r]];
246 return lhe.compare(rhe) < 0;
Adam Lesinski282e1812014-01-23 18:17:42 -0800247}
248
249void StringPool::sortByConfig()
250{
251 LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted.");
252
253 const size_t N = mEntryArray.size();
254
255 // This is a vector that starts out with a 1:1 mapping to entries
256 // in the array, which we will sort to come up with the desired order.
257 // At that point it maps from the new position in the array to the
258 // original position the entry appeared.
259 Vector<size_t> newPosToOriginalPos;
260 newPosToOriginalPos.setCapacity(N);
261 for (size_t i=0; i < N; i++) {
262 newPosToOriginalPos.add(i);
263 }
264
265 // Sort the array.
Andreas Gampe2412f842014-09-30 20:55:57 -0700266 if (kIsDebug) {
267 printf("SORTING STRINGS BY CONFIGURATION...\n");
268 }
Dan Albert0de19ad2014-10-01 11:34:17 -0700269 ConfigSorter sorter(*this);
270 std::sort(newPosToOriginalPos.begin(), newPosToOriginalPos.end(), sorter);
Andreas Gampe2412f842014-09-30 20:55:57 -0700271 if (kIsDebug) {
272 printf("DONE SORTING STRINGS BY CONFIGURATION.\n");
273 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800274
275 // Create the reverse mapping from the original position in the array
276 // to the new position where it appears in the sorted array. This is
277 // so that clients can re-map any positions they had previously stored.
278 mOriginalPosToNewPos = newPosToOriginalPos;
279 for (size_t i=0; i<N; i++) {
280 mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i;
281 }
282
283#if 0
284 SortedVector<entry> entries;
285
286 for (size_t i=0; i<N; i++) {
287 printf("#%d was %d: %s\n", i, newPosToOriginalPos[i],
288 mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string());
289 entries.add(mEntries[mEntryArray[i]]);
290 }
291
292 for (size_t i=0; i<entries.size(); i++) {
293 printf("Sorted config #%d: %s\n", i,
294 entries[i].makeConfigsString().string());
295 }
296#endif
297
298 // Now we rebuild the arrays.
299 Vector<entry> newEntries;
300 Vector<size_t> newEntryArray;
301 Vector<entry_style> newEntryStyleArray;
302 DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset;
303
304 for (size_t i=0; i<N; i++) {
305 // We are filling in new offset 'i'; oldI is where we can find it
306 // in the original data structure.
307 size_t oldI = newPosToOriginalPos[i];
308 // This is the actual entry associated with the old offset.
309 const entry& oldEnt = mEntries[mEntryArray[oldI]];
310 // This is the same entry the last time we added it to the
311 // new entry array, if any.
312 ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI);
313 size_t newOffset;
314 if (newIndexOfOffset < 0) {
315 // This is the first time we have seen the entry, so add
316 // it.
317 newOffset = newEntries.add(oldEnt);
318 newEntries.editItemAt(newOffset).indices.clear();
319 } else {
320 // We have seen this entry before, use the existing one
321 // instead of adding it again.
322 newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset);
323 }
324 // Update the indices to include this new position.
325 newEntries.editItemAt(newOffset).indices.add(i);
326 // And add the offset of the entry to the new entry array.
327 newEntryArray.add(newOffset);
328 // Add any old style to the new style array.
329 if (mEntryStyleArray.size() > 0) {
330 if (oldI < mEntryStyleArray.size()) {
331 newEntryStyleArray.add(mEntryStyleArray[oldI]);
332 } else {
333 newEntryStyleArray.add(entry_style());
334 }
335 }
336 }
337
338 // Now trim any entries at the end of the new style array that are
339 // not needed.
340 for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) {
341 const entry_style& style = newEntryStyleArray[i];
342 if (style.spans.size() > 0) {
343 // That's it.
344 break;
345 }
346 // This one is not needed; remove.
347 newEntryStyleArray.removeAt(i);
348 }
349
350 // All done, install the new data structures and upate mValues with
351 // the new positions.
352 mEntries = newEntries;
353 mEntryArray = newEntryArray;
354 mEntryStyleArray = newEntryStyleArray;
355 mValues.clear();
356 for (size_t i=0; i<mEntries.size(); i++) {
357 const entry& ent = mEntries[i];
358 mValues.add(ent.value, ent.indices[0]);
359 }
360
361#if 0
362 printf("FINAL SORTED STRING CONFIGS:\n");
363 for (size_t i=0; i<mEntries.size(); i++) {
364 const entry& ent = mEntries[i];
365 printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(),
366 String8(ent.value).string());
367 }
368#endif
369}
370
371sp<AaptFile> StringPool::createStringBlock()
372{
373 sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
374 String8());
375 status_t err = writeStringBlock(pool);
376 return err == NO_ERROR ? pool : NULL;
377}
378
379#define ENCODE_LENGTH(str, chrsz, strSize) \
380{ \
381 size_t maxMask = 1 << ((chrsz*8)-1); \
382 size_t maxSize = maxMask-1; \
383 if (strSize > maxSize) { \
384 *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \
385 } \
386 *str++ = strSize; \
387}
388
389status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
390{
391 // Allow appending. Sorry this is a little wacky.
392 if (pool->getSize() > 0) {
393 sp<AaptFile> block = createStringBlock();
394 if (block == NULL) {
395 return UNKNOWN_ERROR;
396 }
397 ssize_t res = pool->writeData(block->getData(), block->getSize());
398 return (res >= 0) ? (status_t)NO_ERROR : res;
399 }
400
401 // First we need to add all style span names to the string pool.
402 // We do this now (instead of when the span is added) so that these
403 // will appear at the end of the pool, not disrupting the order
404 // our client placed their own strings in it.
405
406 const size_t STYLES = mEntryStyleArray.size();
407 size_t i;
408
409 for (i=0; i<STYLES; i++) {
410 entry_style& style = mEntryStyleArray.editItemAt(i);
411 const size_t N = style.spans.size();
412 for (size_t i=0; i<N; i++) {
413 entry_style_span& span = style.spans.editItemAt(i);
414 ssize_t idx = add(span.name, true);
415 if (idx < 0) {
416 fprintf(stderr, "Error adding span for style tag '%s'\n",
417 String8(span.name).string());
418 return idx;
419 }
420 span.span.name.index = (uint32_t)idx;
421 }
422 }
423
424 const size_t ENTRIES = mEntryArray.size();
425
426 // Now build the pool of unique strings.
427
428 const size_t STRINGS = mEntries.size();
429 const size_t preSize = sizeof(ResStringPool_header)
430 + (sizeof(uint32_t)*ENTRIES)
431 + (sizeof(uint32_t)*STYLES);
432 if (pool->editData(preSize) == NULL) {
433 fprintf(stderr, "ERROR: Out of memory for string pool\n");
434 return NO_MEMORY;
435 }
436
437 const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
438
439 size_t strPos = 0;
440 for (i=0; i<STRINGS; i++) {
441 entry& ent = mEntries.editItemAt(i);
442 const size_t strSize = (ent.value.size());
443 const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
444 charSize*2 : charSize;
445
446 String8 encStr;
447 if (mUTF8) {
448 encStr = String8(ent.value);
449 }
450
451 const size_t encSize = mUTF8 ? encStr.size() : 0;
452 const size_t encLenSize = mUTF8 ?
453 (encSize > (size_t)(1<<((charSize*8)-1))-1 ?
454 charSize*2 : charSize) : 0;
455
456 ent.offset = strPos;
457
458 const size_t totalSize = lenSize + encLenSize +
459 ((mUTF8 ? encSize : strSize)+1)*charSize;
460
461 void* dat = (void*)pool->editData(preSize + strPos + totalSize);
462 if (dat == NULL) {
463 fprintf(stderr, "ERROR: Out of memory for string pool\n");
464 return NO_MEMORY;
465 }
466 dat = (uint8_t*)dat + preSize + strPos;
467 if (mUTF8) {
468 uint8_t* strings = (uint8_t*)dat;
469
470 ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
471
472 ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
473
474 strncpy((char*)strings, encStr, encSize+1);
475 } else {
Dan Albertf348c152014-09-08 18:28:00 -0700476 char16_t* strings = (char16_t*)dat;
Adam Lesinski282e1812014-01-23 18:17:42 -0800477
Dan Albertf348c152014-09-08 18:28:00 -0700478 ENCODE_LENGTH(strings, sizeof(char16_t), strSize)
Adam Lesinski282e1812014-01-23 18:17:42 -0800479
480 strcpy16_htod(strings, ent.value);
481 }
482
483 strPos += totalSize;
484 }
485
486 // Pad ending string position up to a uint32_t boundary.
487
488 if (strPos&0x3) {
489 size_t padPos = ((strPos+3)&~0x3);
490 uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos);
491 if (dat == NULL) {
492 fprintf(stderr, "ERROR: Out of memory padding string pool\n");
493 return NO_MEMORY;
494 }
495 memset(dat+preSize+strPos, 0, padPos-strPos);
496 strPos = padPos;
497 }
498
499 // Build the pool of style spans.
500
501 size_t styPos = strPos;
502 for (i=0; i<STYLES; i++) {
503 entry_style& ent = mEntryStyleArray.editItemAt(i);
504 const size_t N = ent.spans.size();
505 const size_t totalSize = (N*sizeof(ResStringPool_span))
506 + sizeof(ResStringPool_ref);
507
508 ent.offset = styPos-strPos;
509 uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize);
510 if (dat == NULL) {
511 fprintf(stderr, "ERROR: Out of memory for string styles\n");
512 return NO_MEMORY;
513 }
514 ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos);
515 for (size_t i=0; i<N; i++) {
516 span->name.index = htodl(ent.spans[i].span.name.index);
517 span->firstChar = htodl(ent.spans[i].span.firstChar);
518 span->lastChar = htodl(ent.spans[i].span.lastChar);
519 span++;
520 }
521 span->name.index = htodl(ResStringPool_span::END);
522
523 styPos += totalSize;
524 }
525
526 if (STYLES > 0) {
527 // Add full terminator at the end (when reading we validate that
528 // the end of the pool is fully terminated to simplify error
529 // checking).
530 size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
531 uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra);
532 if (dat == NULL) {
533 fprintf(stderr, "ERROR: Out of memory for string styles\n");
534 return NO_MEMORY;
535 }
536 uint32_t* p = (uint32_t*)(dat+preSize+styPos);
537 while (extra > 0) {
538 *p++ = htodl(ResStringPool_span::END);
539 extra -= sizeof(uint32_t);
540 }
541 styPos += extra;
542 }
543
544 // Write header.
545
546 ResStringPool_header* header =
547 (ResStringPool_header*)pool->padData(sizeof(uint32_t));
548 if (header == NULL) {
549 fprintf(stderr, "ERROR: Out of memory for string pool\n");
550 return NO_MEMORY;
551 }
552 memset(header, 0, sizeof(*header));
553 header->header.type = htods(RES_STRING_POOL_TYPE);
554 header->header.headerSize = htods(sizeof(*header));
555 header->header.size = htodl(pool->getSize());
556 header->stringCount = htodl(ENTRIES);
557 header->styleCount = htodl(STYLES);
558 if (mUTF8) {
559 header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
560 }
561 header->stringsStart = htodl(preSize);
562 header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
563
564 // Write string index array.
565
566 uint32_t* index = (uint32_t*)(header+1);
567 for (i=0; i<ENTRIES; i++) {
568 entry& ent = mEntries.editItemAt(mEntryArray[i]);
569 *index++ = htodl(ent.offset);
Andreas Gampe2412f842014-09-30 20:55:57 -0700570 if (kIsDebug) {
571 printf("Writing entry #%zu: \"%s\" ent=%zu off=%zu\n",
572 i,
573 String8(ent.value).string(),
574 mEntryArray[i],
575 ent.offset);
576 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800577 }
578
579 // Write style index array.
580
581 for (i=0; i<STYLES; i++) {
582 *index++ = htodl(mEntryStyleArray[i].offset);
583 }
584
585 return NO_ERROR;
586}
587
588ssize_t StringPool::offsetForString(const String16& val) const
589{
590 const Vector<size_t>* indices = offsetsForString(val);
591 ssize_t res = indices != NULL && indices->size() > 0 ? indices->itemAt(0) : -1;
Andreas Gampe2412f842014-09-30 20:55:57 -0700592 if (kIsDebug) {
593 printf("Offset for string %s: %zd (%s)\n", String8(val).string(), SSIZE(res),
594 res >= 0 ? String8(mEntries[mEntryArray[res]].value).string() : String8());
595 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800596 return res;
597}
598
599const Vector<size_t>* StringPool::offsetsForString(const String16& val) const
600{
601 ssize_t pos = mValues.valueFor(val);
602 if (pos < 0) {
603 return NULL;
604 }
605 return &mEntries[mEntryArray[pos]].indices;
606}