From: Dianne Hackborn Date: Tue, 31 Jan 2012 19:27:43 +0000 (-0800) Subject: aapt now sorts the strings in the resource string pool. X-Git-Url: https://git.saurik.com/android/aapt.git/commitdiff_plain/4f41c7f7892c7e65393723aa8fdffc236d959fb7 aapt now sorts the strings in the resource string pool. In our current environment with very many translations, this can save a lot of RAM -- for example over 200K in Gmail just by sorting the strings in the Gmail .apk (not the framework). Also add a new aapt command to print the contents of the resource table string pool. Change-Id: I1da037b3e2c377b890833ff57ab158965314ac48 --- diff --git a/AaptAssets.h b/AaptAssets.h index d5345b2..c5a397c 100644 --- a/AaptAssets.h +++ b/AaptAssets.h @@ -53,17 +53,6 @@ enum { AXIS_END = AXIS_VERSION, }; -enum { - SDK_CUPCAKE = 3, - SDK_DONUT = 4, - SDK_ECLAIR = 5, - SDK_ECLAIR_0_1 = 6, - SDK_MR1 = 7, - SDK_FROYO = 8, - SDK_HONEYCOMB_MR2 = 13, - SDK_ICE_CREAM_SANDWICH = 14, -}; - /** * This structure contains a specific variation of a single file out * of all the variations it can have that we can have. diff --git a/Bundle.h b/Bundle.h index 2d1060b..8e3a1c9 100644 --- a/Bundle.h +++ b/Bundle.h @@ -14,6 +14,18 @@ #include #include +enum { + SDK_CUPCAKE = 3, + SDK_DONUT = 4, + SDK_ECLAIR = 5, + SDK_ECLAIR_0_1 = 6, + SDK_MR1 = 7, + SDK_FROYO = 8, + SDK_HONEYCOMB_MR2 = 13, + SDK_ICE_CREAM_SANDWICH = 14, + SDK_ICE_CREAM_SANDWICH_MR1 = 15, +}; + /* * Things we can do. */ @@ -82,7 +94,6 @@ public: void setRequireLocalization(bool val) { mRequireLocalization = val; } bool getPseudolocalize(void) const { return mPseudolocalize; } void setPseudolocalize(bool val) { mPseudolocalize = val; } - bool getWantUTF16(void) const { return mWantUTF16; } void setWantUTF16(bool val) { mWantUTF16 = val; } bool getValues(void) const { return mValues; } void setValues(bool val) { mValues = val; } @@ -103,6 +114,10 @@ public: bool getGenDependencies() { return mGenDependencies; } void setGenDependencies(bool val) { mGenDependencies = val; } + bool getUTF16StringsOption() { + return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); + } + /* * Input options. */ diff --git a/Command.cpp b/Command.cpp index 89942de..607056a 100644 --- a/Command.cpp +++ b/Command.cpp @@ -479,6 +479,11 @@ int doDump(Bundle* bundle) #ifndef HAVE_ANDROID_OS res.print(bundle->getValues()); #endif + + } else if (strcmp("strings", option) == 0) { + const ResStringPool* pool = res.getTableStringBlock(0); + printStringPool(pool); + } else if (strcmp("xmltree", option) == 0) { if (bundle->getFileSpecCount() < 3) { fprintf(stderr, "ERROR: no dump xmltree resource file specified\n"); @@ -1382,7 +1387,7 @@ int doDump(Bundle* bundle) delete dir; } } else if (strcmp("badger", option) == 0) { - printf(CONSOLE_DATA); + printf("%s", CONSOLE_DATA); } else if (strcmp("configurations", option) == 0) { Vector configs; res.getConfigurations(&configs); diff --git a/Resource.cpp b/Resource.cpp index 1ecf7da..c0fe538 100644 --- a/Resource.cpp +++ b/Resource.cpp @@ -847,8 +847,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) * request UTF-16 encoding and the parameters of this package * allow UTF-8 to be used. */ - if (!bundle->getWantUTF16() - && bundle->isMinSdkAtLeast(SDK_FROYO)) { + if (!bundle->getUTF16StringsOption()) { xmlFlags |= XML_COMPILE_UTF8; } diff --git a/ResourceTable.cpp b/ResourceTable.cpp index fdb39ca..f59bba2 100644 --- a/ResourceTable.cpp +++ b/ResourceTable.cpp @@ -2047,7 +2047,8 @@ bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, uint32_t attrID, const Vector* style, String16* outStr, void* accessorCookie, - uint32_t attrType) + uint32_t attrType, const String8* configTypeName, + const ConfigDescription* config) { String16 finalStr; @@ -2075,10 +2076,19 @@ bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, if (outValue->dataType == outValue->TYPE_STRING) { // Should do better merging styles. if (pool) { + String8 configStr; + if (config != NULL) { + configStr = config->toString(); + } else { + configStr = "(null)"; + } + NOISY(printf("Adding to pool string style #%d config %s: %s\n", + style != NULL ? style->size() : 0, + configStr.string(), String8(finalStr).string())); if (style != NULL && style->size() > 0) { - outValue->data = pool->add(finalStr, *style); + outValue->data = pool->add(finalStr, *style, configTypeName, config); } else { - outValue->data = pool->add(finalStr, true); + outValue->data = pool->add(finalStr, true, configTypeName, config); } } else { // Caller will fill this in later. @@ -2537,16 +2547,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) return err; } + const ConfigDescription nullConfig; + const size_t N = mOrderedPackages.size(); size_t pi; const static String16 mipmap16("mipmap"); - bool useUTF8 = !bundle->getWantUTF16() && bundle->isMinSdkAtLeast(SDK_FROYO); + bool useUTF8 = !bundle->getUTF16StringsOption(); // Iterate through all data, collecting all values (strings, // references, etc). StringPool valueStrings = StringPool(false, useUTF8); + Vector > allEntries; for (pi=0; pi p = mOrderedPackages.itemAt(pi); if (p->getTypes().size() == 0) { @@ -2567,6 +2580,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) const String16 typeName(t->getName()); typeStrings.add(typeName, false); + // This is a hack to tweak the sorting order of the final strings, + // to put stuff that is generally not language-specific first. + String8 configTypeName(typeName); + if (configTypeName == "drawable" || configTypeName == "layout" + || configTypeName == "color" || configTypeName == "anim" + || configTypeName == "interpolator" || configTypeName == "animator" + || configTypeName == "xml" || configTypeName == "menu" + || configTypeName == "mipmap" || configTypeName == "raw") { + configTypeName = "1complex"; + } else { + configTypeName = "2value"; + } + const bool filterable = (typeName != mipmap16); const size_t N = t->getOrderedConfigs().size(); @@ -2586,10 +2612,21 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) continue; } e->setNameIndex(keyStrings.add(e->getName(), true)); - status_t err = e->prepareFlatten(&valueStrings, this); + + // If this entry has no values for other configs, + // and is the default config, then it is special. Otherwise + // we want to add it with the config info. + ConfigDescription* valueConfig = NULL; + if (N != 1 || config == nullConfig) { + valueConfig = &config; + } + + status_t err = e->prepareFlatten(&valueStrings, this, + &configTypeName, &config); if (err != NO_ERROR) { return err; } + allEntries.add(e); } } } @@ -2598,6 +2635,17 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) p->setKeyStrings(keyStrings.createStringBlock()); } + if (bundle->getOutputAPKFile() != NULL) { + // Now we want to sort the value strings for better locality. This will + // cause the positions of the strings to change, so we need to go back + // through out resource entries and update them accordingly. Only need + // to do this if actually writing the output file. + valueStrings.sortByConfig(); + for (pi=0; piremapStringValue(&valueStrings); + } + } + ssize_t strAmt = 0; // Now build the array of package chunks. @@ -3137,14 +3185,16 @@ status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table, return hasErrors ? UNKNOWN_ERROR : NO_ERROR; } -status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table) +status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config) { if (mType == TYPE_ITEM) { Item& it = mItem; AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value)); if (!table->stringToValue(&it.parsedValue, strings, it.value, false, true, 0, - &it.style, NULL, &ac, mItemFormat)) { + &it.style, NULL, &ac, mItemFormat, + configTypeName, config)) { return UNKNOWN_ERROR; } } else if (mType == TYPE_BAG) { @@ -3155,7 +3205,8 @@ status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable AccessorCookie ac(it.sourcePos, String8(key), String8(it.value)); if (!table->stringToValue(&it.parsedValue, strings, it.value, false, true, it.bagKeyId, - &it.style, NULL, &ac, it.format)) { + &it.style, NULL, &ac, it.format, + configTypeName, config)) { return UNKNOWN_ERROR; } } @@ -3167,6 +3218,29 @@ status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable return NO_ERROR; } +status_t ResourceTable::Entry::remapStringValue(StringPool* strings) +{ + if (mType == TYPE_ITEM) { + Item& it = mItem; + if (it.parsedValue.dataType == Res_value::TYPE_STRING) { + it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); + } + } else if (mType == TYPE_BAG) { + const size_t N = mBag.size(); + for (size_t i=0; imapOriginalPosToNewPos(it.parsedValue.data); + } + } + } else { + mPos.error("Error: entry %s is not a single item or a bag.\n", + String8(mName).string()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp& data, bool isPublic) { size_t amt = 0; diff --git a/ResourceTable.h b/ResourceTable.h index 8123bb3..a3e0666 100644 --- a/ResourceTable.h +++ b/ResourceTable.h @@ -76,6 +76,37 @@ public: class Type; class Entry; + struct ConfigDescription : public ResTable_config { + ConfigDescription() { + memset(this, 0, sizeof(*this)); + size = sizeof(ResTable_config); + } + ConfigDescription(const ResTable_config&o) { + *static_cast(this) = o; + size = sizeof(ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast(this) = o; + } + + ConfigDescription& operator=(const ResTable_config& o) { + *static_cast(this) = o; + size = sizeof(ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast(this) = o; + return *this; + } + + inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } + inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } + inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } + inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } + inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } + inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } + }; + ResourceTable(Bundle* bundle, const String16& assetsPackage); status_t addIncludedResources(Bundle* bundle, const sp& assets); @@ -183,7 +214,9 @@ public: uint32_t attrID, const Vector* style = NULL, String16* outStr = NULL, void* accessorCookie = NULL, - uint32_t attrType = ResTable_map::TYPE_ANY); + uint32_t attrType = ResTable_map::TYPE_ANY, + const String8* configTypeName = NULL, + const ConfigDescription* config = NULL); status_t assignResourceIds(); status_t addSymbols(const sp& outSymbols = NULL); @@ -305,7 +338,10 @@ public: status_t assignResourceIds(ResourceTable* table, const String16& package); - status_t prepareFlatten(StringPool* strings, ResourceTable* table); + status_t prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config); + + status_t remapStringValue(StringPool* strings); ssize_t flatten(Bundle*, const sp& data, bool isPublic); @@ -322,37 +358,6 @@ public: uint32_t mParentId; SourcePos mPos; }; - - struct ConfigDescription : public ResTable_config { - ConfigDescription() { - memset(this, 0, sizeof(*this)); - size = sizeof(ResTable_config); - } - ConfigDescription(const ResTable_config&o) { - *static_cast(this) = o; - size = sizeof(ResTable_config); - } - ConfigDescription(const ConfigDescription&o) { - *static_cast(this) = o; - } - - ConfigDescription& operator=(const ResTable_config& o) { - *static_cast(this) = o; - size = sizeof(ResTable_config); - return *this; - } - ConfigDescription& operator=(const ConfigDescription& o) { - *static_cast(this) = o; - return *this; - } - - inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } - inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } - inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } - inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } - inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } - inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } - }; class ConfigList : public RefBase { public: diff --git a/StringPool.cpp b/StringPool.cpp index 9a0a1c4..fe88e37 100644 --- a/StringPool.cpp +++ b/StringPool.cpp @@ -5,8 +5,10 @@ // #include "StringPool.h" +#include "ResourceTable.h" #include +#include #if HAVE_PRINTF_ZD # define ZD "%zd" @@ -30,16 +32,70 @@ void strcpy16_htod(uint16_t* dst, const uint16_t* src) void printStringPool(const ResStringPool* pool) { + SortedVector uniqueStrings; + const size_t N = pool->size(); + for (size_t i=0; iisUTF8()) { + uniqueStrings.add(pool->string8At(i, &len)); + } else { + uniqueStrings.add(pool->stringAt(i, &len)); + } + } + + printf("String pool of " ZD " unique %s %s strings, " ZD " entries and " + ZD " styles using " ZD " bytes:\n", + (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16", + pool->isSorted() ? "sorted" : "non-sorted", + (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes()); + const size_t NS = pool->size(); for (size_t s=0; sstring8At(s, &len); - if (str == NULL) { - str = String8(pool->stringAt(s, &len)).string(); + String8 str = pool->string8ObjectAt(s); + printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string()); + } +} + +String8 StringPool::entry::makeConfigsString() const { + String8 configStr(configTypeName); + if (configStr.size() > 0) configStr.append(" "); + if (configs.size() > 0) { + for (size_t j=0; j 0) configStr.append(", "); + configStr.append(configs[j].toString()); } + } else { + configStr = "(none)"; + } + return configStr; +} - printf("String #" ZD ": %s\n", (ZD_TYPE) s, str); +int StringPool::entry::compare(const entry& o) const { + // Strings with styles go first, to reduce the size of the + // styles array. + if (hasStyles) { + return o.hasStyles ? 0 : -1; + } + if (o.hasStyles) { + return 1; + } + int comp = configTypeName.compare(o.configTypeName); + if (comp != 0) { + return comp; } + const size_t LHN = configs.size(); + const size_t RHN = o.configs.size(); + size_t i=0; + while (i < LHN && i < RHN) { + comp = configs[i].compareLogical(o.configs[i]); + if (comp != 0) { + return comp; + } + i++; + } + if (LHN < RHN) return -1; + else if (LHN > RHN) return 1; + return 0; } StringPool::StringPool(bool sorted, bool utf8) @@ -47,14 +103,16 @@ StringPool::StringPool(bool sorted, bool utf8) { } -ssize_t StringPool::add(const String16& value, bool mergeDuplicates) +ssize_t StringPool::add(const String16& value, bool mergeDuplicates, + const String8* configTypeName, const ResTable_config* config) { - return add(String16(), value, mergeDuplicates); + return add(String16(), value, mergeDuplicates, configTypeName, config); } -ssize_t StringPool::add(const String16& value, const Vector& spans) +ssize_t StringPool::add(const String16& value, const Vector& spans, + const String8* configTypeName, const ResTable_config* config) { - ssize_t res = add(String16(), value, false); + ssize_t res = add(String16(), value, false, configTypeName, config); if (res >= 0) { addStyleSpans(res, spans); } @@ -62,7 +120,7 @@ ssize_t StringPool::add(const String16& value, const Vector& s } ssize_t StringPool::add(const String16& ident, const String16& value, - bool mergeDuplicates) + bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config) { if (ident.size() > 0) { ssize_t idx = mIdents.valueFor(ident); @@ -84,20 +142,43 @@ ssize_t StringPool::add(const String16& ident, const String16& value, } } + if (configTypeName != NULL) { + entry& ent = mEntries.editItemAt(eidx); + NOISY(printf("*** adding config type name %s, was %s\n", + configTypeName->string(), ent.configTypeName.string())); + if (ent.configTypeName.size() <= 0) { + ent.configTypeName = *configTypeName; + } else if (ent.configTypeName != *configTypeName) { + ent.configTypeName = " "; + } + } + + if (config != NULL) { + // Add this to the set of configs associated with the string. + entry& ent = mEntries.editItemAt(eidx); + size_t addPos; + for (addPos=0; addPos= 0) { + if (cmp > 0) { + NOISY(printf("*** inserting config: %s\n", config->toString().string())); + ent.configs.insertAt(*config, addPos); + } + break; + } + } + if (addPos >= ent.configs.size()) { + NOISY(printf("*** adding config: %s\n", config->toString().string())); + ent.configs.add(*config); + } + } + const bool first = vidx < 0; if (first || !mergeDuplicates) { pos = mEntryArray.add(eidx); if (first) { vidx = mValues.add(value, pos); - const size_t N = mEntryArrayToValues.size(); - for (size_t i=0; i= vidx) { - e++; - } - } } - mEntryArrayToValues.add(vidx); if (!mSorted) { entry& ent = mEntries.editItemAt(eidx); ent.indices.add(pos); @@ -147,6 +228,7 @@ status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span) entry_style& style = mEntryStyleArray.editItemAt(idx); style.spans.add(span); + mEntries.editItemAt(mEntryArray[idx]).hasStyles = true; return NO_ERROR; } @@ -169,6 +251,132 @@ size_t StringPool::countIdentifiers() const return mIdents.size(); } +int StringPool::config_sort(const size_t* lhs, const size_t* rhs, void* state) +{ + StringPool* pool = (StringPool*)state; + const entry& lhe = pool->mEntries[pool->mEntryArray[*lhs]]; + const entry& rhe = pool->mEntries[pool->mEntryArray[*rhs]]; + return lhe.compare(rhe); +} + +void StringPool::sortByConfig() +{ + LOG_ALWAYS_FATAL_IF(mSorted, "Can't sort string pool containing identifiers."); + LOG_ALWAYS_FATAL_IF(mIdents.size() > 0, "Can't sort string pool containing identifiers."); + LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted."); + + const size_t N = mEntryArray.size(); + + // This is a vector that starts out with a 1:1 mapping to entries + // in the array, which we will sort to come up with the desired order. + // At that point it maps from the new position in the array to the + // original position the entry appeared. + Vector newPosToOriginalPos; + for (size_t i=0; i entries; + + for (size_t i=0; i newEntries; + Vector newEntryArray; + Vector newEntryStyleArray; + DefaultKeyedVector origOffsetToNewOffset; + + for (size_t i=0; i 0) { + if (oldI < mEntryStyleArray.size()) { + newEntryStyleArray.add(mEntryStyleArray[oldI]); + } else { + newEntryStyleArray.add(entry_style()); + } + } + } + + // Now trim any entries at the end of the new style array that are + // not needed. + for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) { + const entry_style& style = newEntryStyleArray[i]; + if (style.spans.size() > 0) { + // That's it. + break; + } + // This one is not needed; remove. + newEntryStyleArray.removeAt(i); + } + + // All done, install the new data structures and upate mValues with + // the new positions. + mEntries = newEntries; + mEntryArray = newEntryArray; + mEntryStyleArray = newEntryStyleArray; + mValues.clear(); + for (size_t i=0; i StringPool::createStringBlock() { sp pool = new AaptFile(String8(), AaptGroupEntry(), diff --git a/StringPool.h b/StringPool.h index 7275259..255bdbf 100644 --- a/StringPool.h +++ b/StringPool.h @@ -40,12 +40,28 @@ class StringPool public: struct entry { entry() : offset(0) { } - entry(const String16& _value) : value(_value), offset(0) { } - entry(const entry& o) : value(o.value), offset(o.offset), indices(o.indices) { } + entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { } + entry(const entry& o) : value(o.value), offset(o.offset), + hasStyles(o.hasStyles), indices(o.indices), + configTypeName(o.configTypeName), configs(o.configs) { } String16 value; size_t offset; + bool hasStyles; Vector indices; + String8 configTypeName; + Vector configs; + + String8 makeConfigsString() const; + + int compare(const entry& o) const; + + inline bool operator<(const entry& o) const { return compare(o) < 0; } + inline bool operator<=(const entry& o) const { return compare(o) <= 0; } + inline bool operator==(const entry& o) const { return compare(o) == 0; } + inline bool operator!=(const entry& o) const { return compare(o) != 0; } + inline bool operator>=(const entry& o) const { return compare(o) >= 0; } + inline bool operator>(const entry& o) const { return compare(o) > 0; } }; struct entry_style_span { @@ -84,12 +100,15 @@ public: * if this string pool is sorted, the returned index will not be valid * when the pool is finally written. */ - ssize_t add(const String16& value, bool mergeDuplicates = false); + ssize_t add(const String16& value, bool mergeDuplicates = false, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); - ssize_t add(const String16& value, const Vector& spans); + ssize_t add(const String16& value, const Vector& spans, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); ssize_t add(const String16& ident, const String16& value, - bool mergeDuplicates = false); + bool mergeDuplicates = false, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); status_t addStyleSpan(size_t idx, const String16& name, uint32_t start, uint32_t end); @@ -102,6 +121,18 @@ public: size_t countIdentifiers() const; + // Sort the contents of the string block by the configuration associated + // with each item. After doing this you can use mapOriginalPosToNewPos() + // to find out the new position given the position originall returned by + // add(). + void sortByConfig(); + + // For use after sortByConfig() to map from the original position of + // a string to its new sorted position. + size_t mapOriginalPosToNewPos(size_t originalPos) const { + return mOriginalPosToNewPos.itemAt(originalPos); + } + sp createStringBlock(); status_t writeStringBlock(const sp& pool); @@ -125,27 +156,41 @@ public: const Vector* offsetsForString(const String16& val) const; private: + static int config_sort(const size_t* lhs, const size_t* rhs, void* state); + const bool mSorted; const bool mUTF8; - // Raw array of unique strings, in some arbitrary order. + + // The following data structures represent the actual structures + // that will be generated for the final string pool. + + // Raw array of unique strings, in some arbitrary order. This is the + // actual strings that appear in the final string pool, in the order + // that they will be written. Vector mEntries; // Array of indices into mEntries, in the order they were // added to the pool. This can be different than mEntries // if the same string was added multiple times (it will appear // once in mEntries, with multiple occurrences in this array). + // This is the lookup array that will be written for finding + // the string for each offset/position in the string pool. Vector mEntryArray; // Optional style span information associated with each index of // mEntryArray. Vector mEntryStyleArray; - // Mapping from indices in mEntryArray to indices in mValues. - Vector mEntryArrayToValues; + + // The following data structures are used for book-keeping as the + // string pool is constructed. + // Unique set of all the strings added to the pool, mapped to // the first index of mEntryArray where the value was added. DefaultKeyedVector mValues; // Unique set of all (optional) identifiers of strings in the // pool, mapping to indices in mEntries. DefaultKeyedVector mIdents; - + // This array maps from the original position a string was placed at + // in mEntryArray to its new position after being sorted with sortByConfig(). + Vector mOriginalPosToNewPos; }; #endif