AAPT2: Separate out the various steps

An early refactor. Some ideas became clearer as
development continued. Now the various phases are much
clearer and more easily reusable.

Also added a ton of tests!

Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
new file mode 100644
index 0000000..427ab18
--- /dev/null
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "flatten/ChunkWriter.h"
+#include "flatten/ResourceTypeExtensions.h"
+#include "flatten/TableFlattener.h"
+#include "util/BigBuffer.h"
+
+#include <type_traits>
+#include <numeric>
+#include <utils/misc.h>
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+template <typename T>
+static bool cmpIds(const T* a, const T* b) {
+    return a->id.value() < b->id.value();
+}
+
+static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
+    if (len == 0) {
+        return;
+    }
+
+    size_t i;
+    const char16_t* srcData = src.data();
+    for (i = 0; i < len - 1 && i < src.size(); i++) {
+        dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
+    }
+    dst[i] = 0;
+}
+
+struct FlatEntry {
+    ResourceEntry* entry;
+    Value* value;
+    uint32_t entryKey;
+    uint32_t sourcePathKey;
+    uint32_t sourceLine;
+};
+
+struct SymbolWriter {
+    struct Entry {
+        StringPool::Ref name;
+        size_t offset;
+    };
+
+    StringPool pool;
+    std::vector<Entry> symbols;
+
+    void addSymbol(const ResourceNameRef& name, size_t offset) {
+        symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" +
+                                              toString(name.type).toString() + u"/" +
+                                              name.entry.toString()), offset });
+    }
+};
+
+struct MapFlattenVisitor : public RawValueVisitor {
+    using RawValueVisitor::visit;
+
+    SymbolWriter* mSymbols;
+    FlatEntry* mEntry;
+    BigBuffer* mBuffer;
+    size_t mEntryCount = 0;
+    Maybe<uint32_t> mParentIdent;
+    Maybe<ResourceNameRef> mParentName;
+
+    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+    }
+
+    void flattenKey(Reference* key, ResTable_map* outEntry) {
+        if (!key->id) {
+            assert(key->name && "reference must have a name");
+
+            outEntry->name.ident = util::hostToDevice32(0);
+            mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                    offsetof(ResTable_map, name));
+        } else {
+            outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+        }
+    }
+
+    void flattenValue(Item* value, ResTable_map* outEntry) {
+        if (Reference* ref = valueCast<Reference>(value)) {
+            if (!ref->id) {
+                assert(ref->name && "reference must have a name");
+
+                mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                                        offsetof(ResTable_map, value) + offsetof(Res_value, data));
+            }
+        }
+
+        bool result = value->flatten(&outEntry->value);
+        assert(result && "flatten failed");
+    }
+
+    void flattenEntry(Reference* key, Item* value) {
+        ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+        flattenKey(key, outEntry);
+        flattenValue(value, outEntry);
+        outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+        mEntryCount++;
+    }
+
+    void visit(Attribute* attr) override {
+        {
+            Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
+            flattenEntry(&key, &val);
+        }
+
+        for (Attribute::Symbol& s : attr->symbols) {
+            BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+            flattenEntry(&s.symbol, &val);
+        }
+    }
+
+    static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+        if (a.key.id) {
+            if (b.key.id) {
+                return a.key.id.value() < b.key.id.value();
+            }
+            return true;
+        } else if (!b.key.id) {
+            return a.key.name.value() < b.key.name.value();
+        }
+        return false;
+    }
+
+    void visit(Style* style) override {
+        if (style->parent) {
+            if (!style->parent.value().id) {
+                assert(style->parent.value().name && "reference must have a name");
+                mParentName = style->parent.value().name;
+            } else {
+                mParentIdent = style->parent.value().id.value().id;
+            }
+        }
+
+        // Sort the style.
+        std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
+
+        for (Style::Entry& entry : style->entries) {
+            flattenEntry(&entry.key, entry.value.get());
+        }
+    }
+
+    void visit(Styleable* styleable) override {
+        for (auto& attrRef : styleable->entries) {
+            BinaryPrimitive val(Res_value{});
+            flattenEntry(&attrRef, &val);
+        }
+    }
+
+    void visit(Array* array) override {
+        for (auto& item : array->items) {
+            ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+            flattenValue(item.get(), outEntry);
+            outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+            mEntryCount++;
+        }
+    }
+
+    void visit(Plural* plural) override {
+        const size_t count = plural->values.size();
+        for (size_t i = 0; i < count; i++) {
+            if (!plural->values[i]) {
+                continue;
+            }
+
+            ResourceId q;
+            switch (i) {
+            case Plural::Zero:
+                q.id = android::ResTable_map::ATTR_ZERO;
+                break;
+
+            case Plural::One:
+                q.id = android::ResTable_map::ATTR_ONE;
+                break;
+
+            case Plural::Two:
+                q.id = android::ResTable_map::ATTR_TWO;
+                break;
+
+            case Plural::Few:
+                q.id = android::ResTable_map::ATTR_FEW;
+                break;
+
+            case Plural::Many:
+                q.id = android::ResTable_map::ATTR_MANY;
+                break;
+
+            case Plural::Other:
+                q.id = android::ResTable_map::ATTR_OTHER;
+                break;
+
+            default:
+                assert(false);
+                break;
+            }
+
+            Reference key(q);
+            flattenEntry(&key, plural->values[i].get());
+        }
+    }
+};
+
+struct PackageFlattener {
+    IDiagnostics* mDiag;
+    TableFlattenerOptions mOptions;
+    ResourceTable* mTable;
+    ResourceTablePackage* mPackage;
+    SymbolWriter mSymbols;
+    StringPool mTypePool;
+    StringPool mKeyPool;
+    StringPool mSourcePool;
+
+    template <typename T>
+    T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
+        static_assert(std::is_same<ResTable_entry, T>::value ||
+                      std::is_same<ResTable_entry_ext, T>::value,
+                      "T must be ResTable_entry or ResTable_entry_ext");
+
+        T* result = buffer->nextBlock<T>();
+        ResTable_entry* outEntry = (ResTable_entry*)(result);
+        if (entry->entry->publicStatus.isPublic) {
+            outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
+        }
+
+        if (entry->value->isWeak()) {
+            outEntry->flags |= ResTable_entry::FLAG_WEAK;
+        }
+
+        if (!entry->value->isItem()) {
+            outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
+        }
+
+        outEntry->key.index = util::hostToDevice32(entry->entryKey);
+        outEntry->size = sizeof(T);
+
+        if (mOptions.useExtendedChunks) {
+            // Write the extra source block. This will be ignored by the Android runtime.
+            ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
+            sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
+            sourceBlock->line = util::hostToDevice32(entry->sourceLine);
+            outEntry->size += sizeof(*sourceBlock);
+        }
+
+        outEntry->flags = util::hostToDevice16(outEntry->flags);
+        outEntry->size = util::hostToDevice16(outEntry->size);
+        return result;
+    }
+
+    bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
+        if (entry->value->isItem()) {
+            writeEntry<ResTable_entry>(entry, buffer);
+            if (Reference* ref = valueCast<Reference>(entry->value)) {
+                if (!ref->id) {
+                    assert(ref->name && "reference must have at least a name");
+                    mSymbols.addSymbol(ref->name.value(),
+                                       buffer->size() + offsetof(Res_value, data));
+                }
+            }
+            Res_value* outValue = buffer->nextBlock<Res_value>();
+            bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+            assert(result && "flatten failed");
+            outValue->size = util::hostToDevice16(sizeof(*outValue));
+        } else {
+            const size_t beforeEntry = buffer->size();
+            ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+            MapFlattenVisitor visitor(&mSymbols, entry, buffer);
+            entry->value->accept(&visitor);
+            outEntry->count = util::hostToDevice32(visitor.mEntryCount);
+            if (visitor.mParentName) {
+                mSymbols.addSymbol(visitor.mParentName.value(),
+                                   beforeEntry + offsetof(ResTable_entry_ext, parent));
+            } else if (visitor.mParentIdent) {
+                outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value());
+            }
+        }
+        return true;
+    }
+
+    bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+                       std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+        ChunkWriter typeWriter(buffer);
+        ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+        typeHeader->id = type->id.value();
+        typeHeader->config = config;
+        typeHeader->config.swapHtoD();
+
+        auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
+            return std::max(max, (uint32_t) a->id.value());
+        };
+
+        // Find the largest entry ID. That is how many entries we will have.
+        const uint32_t entryCount =
+                std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
+
+        typeHeader->entryCount = util::hostToDevice32(entryCount);
+        uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
+
+        assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
+        memset(indices, 0xff, entryCount * sizeof(uint32_t));
+
+        typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
+
+        const size_t entryStart = typeWriter.getBuffer()->size();
+        for (FlatEntry& flatEntry : *entries) {
+            assert(flatEntry.entry->id.value() < entryCount);
+            indices[flatEntry.entry->id.value()] = util::hostToDevice32(
+                    typeWriter.getBuffer()->size() - entryStart);
+            if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
+                mDiag->error(DiagMessage()
+                             << "failed to flatten resource '"
+                             << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
+                             << "' for configuration '" << config << "'");
+                return false;
+            }
+        }
+        typeWriter.finish();
+        return true;
+    }
+
+    std::vector<ResourceTableType*> collectAndSortTypes() {
+        std::vector<ResourceTableType*> sortedTypes;
+        for (auto& type : mPackage->types) {
+            if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+                // Styleables aren't real Resource Types, they are represented in the R.java
+                // file.
+                continue;
+            }
+
+            assert(type->id && "type must have an ID set");
+
+            sortedTypes.push_back(type.get());
+        }
+        std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
+        return sortedTypes;
+    }
+
+    std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
+        // Sort the entries by entry ID.
+        std::vector<ResourceEntry*> sortedEntries;
+        for (auto& entry : type->entries) {
+            assert(entry->id && "entry must have an ID set");
+            sortedEntries.push_back(entry.get());
+        }
+        std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
+        return sortedEntries;
+    }
+
+    bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                         BigBuffer* buffer) {
+        ChunkWriter typeSpecWriter(buffer);
+        ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
+                RES_TABLE_TYPE_SPEC_TYPE);
+        specHeader->id = type->id.value();
+
+        if (sortedEntries->empty()) {
+            typeSpecWriter.finish();
+            return true;
+        }
+
+        // We can't just take the size of the vector. There may be holes in the entry ID space.
+        // Since the entries are sorted by ID, the last one will be the biggest.
+        const size_t numEntries = sortedEntries->back()->id.value() + 1;
+
+        specHeader->entryCount = util::hostToDevice32(numEntries);
+
+        // Reserve space for the masks of each resource in this type. These
+        // show for which configuration axis the resource changes.
+        uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
+
+        const size_t actualNumEntries = sortedEntries->size();
+        for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
+            ResourceEntry* entry = sortedEntries->at(entryIndex);
+
+            // Populate the config masks for this entry.
+
+            if (entry->publicStatus.isPublic) {
+                configMasks[entry->id.value()] |=
+                        util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+            }
+
+            const size_t configCount = entry->values.size();
+            for (size_t i = 0; i < configCount; i++) {
+                const ConfigDescription& config = entry->values[i].config;
+                for (size_t j = i + 1; j < configCount; j++) {
+                    configMasks[entry->id.value()] |= util::hostToDevice32(
+                            config.diff(entry->values[j].config));
+                }
+            }
+        }
+        typeSpecWriter.finish();
+        return true;
+    }
+
+    bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
+                       BigBuffer* buffer) {
+        ChunkWriter publicWriter(buffer);
+        Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
+        publicHeader->typeId = type->id.value();
+
+        for (ResourceEntry* entry : *sortedEntries) {
+            if (entry->publicStatus.isPublic) {
+                // Write the public status of this entry.
+                Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
+                publicEntry->entryId = util::hostToDevice32(entry->id.value());
+                publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
+                        entry->name).getIndex());
+                publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef(
+                        util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
+                if (entry->publicStatus.source.line) {
+                    publicEntry->sourceLine = util::hostToDevice32(
+                            entry->publicStatus.source.line.value());
+                }
+
+                // Don't hostToDevice until the last step.
+                publicHeader->count += 1;
+            }
+        }
+
+        publicHeader->count = util::hostToDevice32(publicHeader->count);
+        publicWriter.finish();
+        return true;
+    }
+
+    bool flattenTypes(BigBuffer* buffer) {
+        // Sort the types by their IDs. They will be inserted into the StringPool in this order.
+        std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
+
+        size_t expectedTypeId = 1;
+        for (ResourceTableType* type : sortedTypes) {
+            // If there is a gap in the type IDs, fill in the StringPool
+            // with empty values until we reach the ID we expect.
+            while (type->id.value() > expectedTypeId) {
+                std::u16string typeName(u"?");
+                typeName += expectedTypeId;
+                mTypePool.makeRef(typeName);
+                expectedTypeId++;
+            }
+            expectedTypeId++;
+            mTypePool.makeRef(toString(type->type));
+
+            std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
+
+            if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
+                return false;
+            }
+
+            if (mOptions.useExtendedChunks) {
+                if (!flattenPublic(type, &sortedEntries, buffer)) {
+                    return false;
+                }
+            }
+
+            // The binary resource table lists resource entries for each configuration.
+            // We store them inverted, where a resource entry lists the values for each
+            // configuration available. Here we reverse this to match the binary table.
+            std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
+            for (ResourceEntry* entry : sortedEntries) {
+                const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+
+                // Group values by configuration.
+                for (auto& configValue : entry->values) {
+                   configToEntryListMap[configValue.config].push_back(FlatEntry{
+                            entry, configValue.value.get(), (uint32_t) keyIndex,
+                            (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16(
+                                    configValue.source.path)).getIndex()),
+                            (uint32_t)(configValue.source.line
+                                    ? configValue.source.line.value() : 0)
+                   });
+                }
+            }
+
+            // Flatten a configuration value.
+            for (auto& entry : configToEntryListMap) {
+                if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    bool flattenPackage(BigBuffer* buffer) {
+        // We must do this before writing the resources, since the string pool IDs may change.
+        mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+            int diff = a.context.priority - b.context.priority;
+            if (diff < 0) return true;
+            if (diff > 0) return false;
+            diff = a.context.config.compare(b.context.config);
+            if (diff < 0) return true;
+            if (diff > 0) return false;
+            return a.value < b.value;
+        });
+        mTable->stringPool.prune();
+
+        const size_t beginningIndex = buffer->size();
+
+        BigBuffer typeBuffer(1024);
+        if (!flattenTypes(&typeBuffer)) {
+            return false;
+        }
+
+        ChunkWriter tableWriter(buffer);
+        ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
+        tableHeader->packageCount = util::hostToDevice32(1);
+
+        SymbolTable_entry* symbolEntryData = nullptr;
+        if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) {
+            // Sort the offsets so we can scan them linearly.
+            std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(),
+                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
+                          return a.offset < b.offset;
+                      });
+
+            ChunkWriter symbolWriter(tableWriter.getBuffer());
+            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
+                    RES_TABLE_SYMBOL_TABLE_TYPE);
+            symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size());
+
+            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(mSymbols.symbols.size());
+            StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool);
+            symbolWriter.finish();
+        }
+
+        if (mOptions.useExtendedChunks && mSourcePool.size() > 0) {
+            // Write out source pool.
+            ChunkWriter srcWriter(tableWriter.getBuffer());
+            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
+            StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool);
+            srcWriter.finish();
+        }
+
+        StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool);
+
+        ChunkWriter pkgWriter(tableWriter.getBuffer());
+        ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
+                RES_TABLE_PACKAGE_TYPE);
+        pkgHeader->id = util::hostToDevice32(mPackage->id.value());
+
+        if (mPackage->name.size() >= NELEM(pkgHeader->name)) {
+            mDiag->error(DiagMessage() <<
+                         "package name '" << mPackage->name << "' is too long");
+            return false;
+        }
+
+        strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name);
+
+        pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
+
+        pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
+        StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
+
+        // Actually write out the symbol entries if we have symbols.
+        if (symbolEntryData) {
+            for (auto& entry : mSymbols.symbols) {
+                symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+
+                // The symbols were all calculated with the typeBuffer offset. We need to
+                // add the beginning of the output buffer.
+                symbolEntryData->offset = util::hostToDevice32(
+                        (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset);
+
+                symbolEntryData++;
+            }
+        }
+
+        // Write out the types and entries.
+        pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer));
+
+        pkgWriter.finish();
+        tableWriter.finish();
+        return true;
+    }
+};
+
+} // namespace
+
+bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
+    for (auto& package : table->packages) {
+        // Only support flattening one package. Since the StringPool is shared between packages
+        // in ResourceTable, we must fail if other packages are present, since their strings
+        // will be included in the final ResourceTable.
+        if (context->getCompilationPackage() != package->name) {
+            context->getDiagnostics()->error(DiagMessage()
+                                             << "resources for package '" << package->name
+                                             << "' can't be flattened when compiling package '"
+                                             << context->getCompilationPackage() << "'");
+            return false;
+        }
+
+        if (!package->id || package->id.value() != context->getPackageId()) {
+            context->getDiagnostics()->error(DiagMessage()
+                                             << "package '" << package->name << "' must have "
+                                             << "package id "
+                                             << std::hex << context->getPackageId() << std::dec);
+            return false;
+        }
+
+        PackageFlattener flattener = {
+                context->getDiagnostics(),
+                mOptions,
+                table,
+                package.get()
+        };
+
+        if (!flattener.flattenPackage(mBuffer)) {
+            return false;
+        }
+        return true;
+    }
+
+    context->getDiagnostics()->error(DiagMessage()
+                                     << "compilation package '" << context->getCompilationPackage()
+                                     << "' not found");
+    return false;
+}
+
+} // namespace aapt