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/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
new file mode 100644
index 0000000..498bc9c
--- /dev/null
+++ b/tools/aapt2/compile/Compile.cpp
@@ -0,0 +1,419 @@
+/*
+ * 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 "ConfigDescription.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "XmlDom.h"
+#include "XmlPullParser.h"
+
+#include "compile/IdAssigner.h"
+#include "compile/Png.h"
+#include "compile/XmlIdCollector.h"
+#include "flatten/FileExportWriter.h"
+#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
+#include "util/Files.h"
+#include "util/Maybe.h"
+#include "util/Util.h"
+
+#include <fstream>
+#include <string>
+
+namespace aapt {
+
+struct ResourcePathData {
+    Source source;
+    std::u16string resourceDir;
+    std::u16string name;
+    std::string extension;
+
+    // Original config str. We keep this because when we parse the config, we may add on
+    // version qualifiers. We want to preserve the original input so the output is easily
+    // computed before hand.
+    std::string configStr;
+    ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
+                                                       std::string* outError) {
+    std::vector<std::string> parts = util::split(path, file::sDirSep);
+    if (parts.size() < 2) {
+        if (outError) *outError = "bad resource path";
+        return {};
+    }
+
+    std::string& dir = parts[parts.size() - 2];
+    StringPiece dirStr = dir;
+
+    StringPiece configStr;
+    ConfigDescription config;
+    size_t dashPos = dir.find('-');
+    if (dashPos != std::string::npos) {
+        configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+        if (!ConfigDescription::parse(configStr, &config)) {
+            if (outError) {
+                std::stringstream errStr;
+                errStr << "invalid configuration '" << configStr << "'";
+                *outError = errStr.str();
+            }
+            return {};
+        }
+        dirStr = dirStr.substr(0, dashPos);
+    }
+
+    std::string& filename = parts[parts.size() - 1];
+    StringPiece name = filename;
+    StringPiece extension;
+    size_t dotPos = filename.find('.');
+    if (dotPos != std::string::npos) {
+        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+        name = name.substr(0, dotPos);
+    }
+
+    return ResourcePathData{
+            Source{ path },
+            util::utf8ToUtf16(dirStr),
+            util::utf8ToUtf16(name),
+            extension.toString(),
+            configStr.toString(),
+            config
+    };
+}
+
+struct CompileOptions {
+    std::string outputPath;
+    bool verbose = false;
+};
+
+static std::string buildIntermediateFilename(const std::string outDir,
+                                             const ResourcePathData& data) {
+    std::stringstream name;
+    name << data.resourceDir;
+    if (!data.configStr.empty()) {
+        name << "-" << data.configStr;
+    }
+    name << "_" << data.name << "." << data.extension << ".flat";
+    std::string outPath = outDir;
+    file::appendPath(&outPath, name.str());
+    return outPath;
+}
+
+static bool compileTable(IAaptContext* context, const CompileOptions& options,
+                         const ResourcePathData& pathData, const std::string& outputPath) {
+    ResourceTable table;
+    table.createPackage(u"", 0x7f);
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+
+        // Parse the values file from XML.
+        XmlPullParser xmlParser(fin);
+        ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+                                 pathData.config);
+        if (!resParser.parse(&xmlParser)) {
+            return false;
+        }
+
+        fin.close();
+    }
+
+    // Assign IDs to prepare the table for flattening.
+    IdAssigner idAssigner;
+    if (!idAssigner.consume(context, &table)) {
+        return false;
+    }
+
+    // Flatten the table.
+    BigBuffer buffer(1024);
+    TableFlattenerOptions tableFlattenerOptions;
+    tableFlattenerOptions.useExtendedChunks = true;
+    TableFlattener flattener(&buffer, tableFlattenerOptions);
+    if (!flattener.consume(context, &table)) {
+        return false;
+    }
+
+    // Build the output filename.
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileXml(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+
+    std::unique_ptr<XmlResource> xmlRes;
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+
+        fin.close();
+    }
+
+    if (!xmlRes) {
+        return false;
+    }
+
+    // Collect IDs that are defined here.
+    XmlIdCollector collector;
+    if (!collector.consume(context, xmlRes.get())) {
+        return false;
+    }
+
+    xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    xmlRes->file.config = pathData.config;
+    xmlRes->file.source = pathData.source;
+
+    BigBuffer buffer(1024);
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
+
+    XmlFlattenerOptions xmlFlattenerOptions;
+    xmlFlattenerOptions.keepRawValues = true;
+    XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
+    if (!flattener.consume(context, xmlRes.get())) {
+        return false;
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Write it to disk.
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compilePng(IAaptContext* context, const CompileOptions& options,
+                       const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(4096);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    {
+        std::ifstream fin(pathData.source.path, std::ifstream::binary);
+        if (!fin) {
+            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
+            return false;
+        }
+
+        Png png(context->getDiagnostics());
+        if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
+            return false;
+        }
+    }
+
+    fileExportWriter.finish();
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+static bool compileFile(IAaptContext* context, const CompileOptions& options,
+                        const ResourcePathData& pathData, const std::string& outputPath) {
+    BigBuffer buffer(256);
+    ResourceFile resFile;
+    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.config = pathData.config;
+    resFile.source = pathData.source;
+
+    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
+
+    std::string errorStr;
+    Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+    if (!f) {
+        context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+        return false;
+    }
+
+    std::ofstream fout(outputPath, std::ofstream::binary);
+    if (!fout) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    // Manually set the size and don't call finish(). This is because we are not copying from
+    // the buffer the entire file.
+    fileExportWriter.getChunkHeader()->size =
+            util::hostToDevice32(buffer.size() + f.value().getDataLength());
+    if (!util::writeAll(fout, buffer)) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+
+    if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
+        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+class CompileContext : public IAaptContext {
+private:
+    StdErrDiagnostics mDiagnostics;
+
+public:
+    IDiagnostics* getDiagnostics() override {
+       return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+       abort();
+       return nullptr;
+    }
+
+    StringPiece16 getCompilationPackage() override {
+       return {};
+    }
+
+    uint8_t getPackageId() override {
+       return 0x7f;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+       abort();
+       return nullptr;
+    }
+};
+
+/**
+ * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ */
+int compile(const std::vector<StringPiece>& args) {
+    CompileOptions options;
+
+    Flags flags = Flags()
+            .requiredFlag("-o", "Output path", &options.outputPath)
+            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+    if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+        return 1;
+    }
+
+    CompileContext context;
+
+    std::vector<ResourcePathData> inputData;
+    inputData.reserve(flags.getArgs().size());
+
+    // Collect data from the path for each input file.
+    for (const std::string& arg : flags.getArgs()) {
+        std::string errorStr;
+        if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+            inputData.push_back(std::move(pathData.value()));
+        } else {
+            context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+            return 1;
+        }
+    }
+
+    bool error = false;
+    for (ResourcePathData& pathData : inputData) {
+        if (options.verbose) {
+            context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
+        }
+
+        if (pathData.resourceDir == u"values") {
+            // Overwrite the extension.
+            pathData.extension = "arsc";
+
+            const std::string outputFilename = buildIntermediateFilename(
+                    options.outputPath, pathData);
+            if (!compileTable(&context, options, pathData, outputFilename)) {
+                error = true;
+            }
+
+        } else {
+            const std::string outputFilename = buildIntermediateFilename(options.outputPath,
+                                                                         pathData);
+            if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+                if (*type != ResourceType::kRaw) {
+                    if (pathData.extension == "xml") {
+                        if (!compileXml(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else if (pathData.extension == "png" || pathData.extension == "9.png") {
+                        if (!compilePng(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    } else {
+                        if (!compileFile(&context, options, pathData, outputFilename)) {
+                            error = true;
+                        }
+                    }
+                } else {
+                    if (!compileFile(&context, options, pathData, outputFilename)) {
+                        error = true;
+                    }
+                }
+            } else {
+                context.getDiagnostics()->error(
+                        DiagMessage() << "invalid file path '" << pathData.source << "'");
+                error = true;
+            }
+        }
+    }
+
+    if (error) {
+        return 1;
+    }
+    return 0;
+}
+
+} // namespace aapt