]> git.saurik.com Git - android/aapt.git/commitdiff
aapt now sorts the strings in the resource string pool.
authorDianne Hackborn <hackbod@google.com>
Tue, 31 Jan 2012 19:27:43 +0000 (11:27 -0800)
committerDianne Hackborn <hackbod@google.com>
Tue, 31 Jan 2012 23:41:30 +0000 (15:41 -0800)
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

AaptAssets.h
Bundle.h
Command.cpp
Resource.cpp
ResourceTable.cpp
ResourceTable.h
StringPool.cpp
StringPool.h

index d5345b277caff2b90150bfbb7a9c30399a5d02d4..c5a397c73390163a5f9e559d4512e244300207a1 100644 (file)
@@ -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.
index 2d1060ba23a07cf826242140af9abbaa69dcba3a..8e3a1c9d11af0b86b94b36c404d68010694fb794 100644 (file)
--- a/Bundle.h
+++ b/Bundle.h
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
+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.
      */
index 89942de57eaa5876886306550028aa97b87da10c..607056a6c1f27395eb131b5e129a938e9368d60c 100644 (file)
@@ -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<ResTable_config> configs;
             res.getConfigurations(&configs);
index 1ecf7daef123edad3147a9a5f848bffd1915bdc2..c0fe5380afa5a6f7f71d29a1e6f1721cc93ff6a0 100644 (file)
@@ -847,8 +847,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& 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;
     }
 
index fdb39ca08e9f555a70dc9ef4babd30952d575c05..f59bba2815a235c09ad79ba925fa0f95e9ce5a3b 100644 (file)
@@ -2047,7 +2047,8 @@ bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
                                   uint32_t attrID,
                                   const Vector<StringPool::entry_style_span>* 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<AaptFile>& 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<sp<Entry> > allEntries;
     for (pi=0; pi<N; pi++) {
         sp<Package> p = mOrderedPackages.itemAt(pi);
         if (p->getTypes().size() == 0) {
@@ -2567,6 +2580,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& 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<AaptFile>& 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<AaptFile>& 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; pi<allEntries.size(); pi++) {
+            allEntries[pi]->remapStringValue(&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; i<N; i++) {
+            Item& it = mBag.editValueAt(i);
+            if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+                it.parsedValue.data = strings->mapOriginalPosToNewPos(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<AaptFile>& data, bool isPublic)
 {
     size_t amt = 0;
index 8123bb3d4226cc0c85f7417381ba51e69ef9508c..a3e066690fba4e747480f9aec8f61c1bc7a643b2 100644 (file)
@@ -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<ResTable_config*>(this) = o;
+            size = sizeof(ResTable_config);
+        }
+        ConfigDescription(const ConfigDescription&o) {
+            *static_cast<ResTable_config*>(this) = o;
+        }
+
+        ConfigDescription& operator=(const ResTable_config& o) {
+            *static_cast<ResTable_config*>(this) = o;
+            size = sizeof(ResTable_config);
+            return *this;
+        }
+        ConfigDescription& operator=(const ConfigDescription& o) {
+            *static_cast<ResTable_config*>(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<AaptAssets>& assets);
@@ -183,7 +214,9 @@ public:
                        uint32_t attrID,
                        const Vector<StringPool::entry_style_span>* 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<AaptSymbols>& 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<AaptFile>& 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<ResTable_config*>(this) = o;
-            size = sizeof(ResTable_config);
-        }
-        ConfigDescription(const ConfigDescription&o) {
-            *static_cast<ResTable_config*>(this) = o;
-        }
-        
-        ConfigDescription& operator=(const ResTable_config& o) {
-            *static_cast<ResTable_config*>(this) = o;
-            size = sizeof(ResTable_config);
-            return *this;
-        }
-        ConfigDescription& operator=(const ConfigDescription& o) {
-            *static_cast<ResTable_config*>(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:
index 9a0a1c46251bf406e5fa598f79f887b0e1271e11..fe88e37c159f9d2eb6d8906655754a1eb8f4fb48 100644 (file)
@@ -5,8 +5,10 @@
 //
 
 #include "StringPool.h"
+#include "ResourceTable.h"
 
 #include <utils/ByteOrder.h>
+#include <utils/SortedVector.h>
 
 #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<const void*> uniqueStrings;
+    const size_t N = pool->size();
+    for (size_t i=0; i<N; i++) {
+        size_t len;
+        if (pool->isUTF8()) {
+            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; s<NS; s++) {
-        size_t len;
-        const char *str = (const char*)pool->string8At(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<configs.size(); j++) {
+            if (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<entry_style_span>& spans)
+ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& 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<entry_style_span>& 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<ent.configs.size(); addPos++) {
+            int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
+            if (cmp >= 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<N; i++) {
-                size_t& e = mEntryArrayToValues.editItemAt(i);
-                if ((ssize_t)e >= 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<size_t> newPosToOriginalPos;
+    for (size_t i=0; i<mEntryArray.size(); i++) {
+        newPosToOriginalPos.add(i);
+    }
+
+    // Sort the array.
+    NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n"));
+    newPosToOriginalPos.sort(config_sort, this);
+    NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n"));
+
+    // Create the reverse mapping from the original position in the array
+    // to the new position where it appears in the sorted array.  This is
+    // so that clients can re-map any positions they had previously stored.
+    mOriginalPosToNewPos = newPosToOriginalPos;
+    for (size_t i=0; i<N; i++) {
+        mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i;
+    }
+
+#if 0
+    SortedVector<entry> entries;
+
+    for (size_t i=0; i<N; i++) {
+        printf("#%d was %d: %s\n", i, newPosToOriginalPos[i],
+                mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string());
+        entries.add(mEntries[mEntryArray[i]]);
+    }
+
+    for (size_t i=0; i<entries.size(); i++) {
+        printf("Sorted config #%d: %s\n", i,
+                entries[i].makeConfigsString().string());
+    }
+#endif
+
+    // Now we rebuild the arrays.
+    Vector<entry> newEntries;
+    Vector<size_t> newEntryArray;
+    Vector<entry_style> newEntryStyleArray;
+    DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset;
+
+    for (size_t i=0; i<N; i++) {
+        // We are filling in new offset 'i'; oldI is where we can find it
+        // in the original data structure.
+        size_t oldI = newPosToOriginalPos[i];
+        // This is the actual entry associated with the old offset.
+        const entry& oldEnt = mEntries[mEntryArray[oldI]];
+        // This is the same entry the last time we added it to the
+        // new entry array, if any.
+        ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI);
+        size_t newOffset;
+        if (newIndexOfOffset < 0) {
+            // This is the first time we have seen the entry, so add
+            // it.
+            newOffset = newEntries.add(oldEnt);
+            newEntries.editItemAt(newOffset).indices.clear();
+        } else {
+            // We have seen this entry before, use the existing one
+            // instead of adding it again.
+            newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset);
+        }
+        // Update the indices to include this new position.
+        newEntries.editItemAt(newOffset).indices.add(i);
+        // And add the offset of the entry to the new entry array.
+        newEntryArray.add(newOffset);
+        // Add any old style to the new style array.
+        if (mEntryStyleArray.size() > 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<mEntries.size(); i++) {
+        const entry& ent = mEntries[i];
+        mValues.add(ent.value, ent.indices[0]);
+    }
+
+#if 0
+    printf("FINAL SORTED STRING CONFIGS:\n");
+    for (size_t i=0; i<mEntries.size(); i++) {
+        const entry& ent = mEntries[i];
+        printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(),
+                String8(ent.value).string());
+    }
+#endif
+}
+
 sp<AaptFile> StringPool::createStringBlock()
 {
     sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
index 727525976cd93dee9584965e6f1ece3997be21d0..255bdbfc0a7f7a5bf194702e49501f5ddfff900e 100644 (file)
@@ -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<size_t> indices;
+        String8 configTypeName;
+        Vector<ResTable_config> 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<entry_style_span>& spans);
+    ssize_t add(const String16& value, const Vector<entry_style_span>& 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<AaptFile> createStringBlock();
 
     status_t writeStringBlock(const sp<AaptFile>& pool);
@@ -125,27 +156,41 @@ public:
     const Vector<size_t>* 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<entry>                           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<size_t>                          mEntryArray;
     // Optional style span information associated with each index of
     // mEntryArray.
     Vector<entry_style>                     mEntryStyleArray;
-    // Mapping from indices in mEntryArray to indices in mValues.
-    Vector<size_t>                          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<String16, ssize_t>   mValues;
     // Unique set of all (optional) identifiers of strings in the
     // pool, mapping to indices in mEntries.
     DefaultKeyedVector<String16, ssize_t>   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<size_t>                          mOriginalPosToNewPos;
 };
 
 #endif