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/util/Files.cpp b/tools/aapt2/util/Files.cpp
new file mode 100644
index 0000000..a81dc7b
--- /dev/null
+++ b/tools/aapt2/util/Files.cpp
@@ -0,0 +1,238 @@
+/*
+ * 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 "util/Files.h"
+#include "util/Util.h"
+
+#include <cerrno>
+#include <cstdio>
+#include <dirent.h>
+#include <string>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+// Windows includes.
+#include <direct.h>
+#endif
+
+namespace aapt {
+namespace file {
+
+FileType getFileType(const StringPiece& path) {
+    struct stat sb;
+    if (stat(path.data(), &sb) < 0) {
+        if (errno == ENOENT || errno == ENOTDIR) {
+            return FileType::kNonexistant;
+        }
+        return FileType::kUnknown;
+    }
+
+    if (S_ISREG(sb.st_mode)) {
+        return FileType::kRegular;
+    } else if (S_ISDIR(sb.st_mode)) {
+        return FileType::kDirectory;
+    } else if (S_ISCHR(sb.st_mode)) {
+        return FileType::kCharDev;
+    } else if (S_ISBLK(sb.st_mode)) {
+        return FileType::kBlockDev;
+    } else if (S_ISFIFO(sb.st_mode)) {
+        return FileType::kFifo;
+#if defined(S_ISLNK)
+    } else if (S_ISLNK(sb.st_mode)) {
+        return FileType::kSymlink;
+#endif
+#if defined(S_ISSOCK)
+    } else if (S_ISSOCK(sb.st_mode)) {
+        return FileType::kSocket;
+#endif
+    } else {
+        return FileType::kUnknown;
+    }
+}
+
+std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
+    DIR* dir = opendir(root.data());
+    if (dir == nullptr) {
+        if (outError) {
+            std::stringstream errorStr;
+            errorStr << "unable to open file: " << strerror(errno);
+            *outError = errorStr.str();
+            return {};
+        }
+    }
+
+    std::vector<std::string> files;
+    dirent* entry;
+    while ((entry = readdir(dir))) {
+        files.emplace_back(entry->d_name);
+    }
+
+    closedir(dir);
+    return files;
+}
+
+inline static int mkdirImpl(const StringPiece& path) {
+#ifdef _WIN32
+    return _mkdir(path.toString().c_str());
+#else
+    return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+}
+
+bool mkdirs(const StringPiece& path) {
+    const char* start = path.begin();
+    const char* end = path.end();
+    for (const char* current = start; current != end; ++current) {
+        if (*current == sDirSep) {
+            StringPiece parentPath(start, current - start);
+            int result = mkdirImpl(parentPath);
+            if (result < 0 && errno != EEXIST) {
+                return false;
+            }
+        }
+    }
+    return mkdirImpl(path) == 0 || errno == EEXIST;
+}
+
+StringPiece getStem(const StringPiece& path) {
+    const char* start = path.begin();
+    const char* end = path.end();
+    for (const char* current = end - 1; current != start - 1; --current) {
+        if (*current == sDirSep) {
+            return StringPiece(start, current - start);
+        }
+    }
+    return {};
+}
+
+StringPiece getFilename(const StringPiece& path) {
+    const char* end = path.end();
+    const char* lastDirSep = path.begin();
+    for (const char* c = path.begin(); c != end; ++c) {
+        if (*c == sDirSep) {
+            lastDirSep = c + 1;
+        }
+    }
+    return StringPiece(lastDirSep, end - lastDirSep);
+}
+
+StringPiece getExtension(const StringPiece& path) {
+    StringPiece filename = getFilename(path);
+    const char* const end = filename.end();
+    const char* c = std::find(filename.begin(), end, '.');
+    if (c != end) {
+        return StringPiece(c, end - c);
+    }
+    return {};
+}
+
+std::string packageToPath(const StringPiece& package) {
+    std::string outPath;
+    for (StringPiece part : util::tokenize<char>(package, '.')) {
+        appendPath(&outPath, part);
+    }
+    return outPath;
+}
+
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
+    std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
+    if (!f) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    int fd = fileno(f.get());
+
+    struct stat fileStats = {};
+    if (fstat(fd, &fileStats) != 0) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+
+    android::FileMap fileMap;
+    if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
+        if (outError) *outError = strerror(errno);
+        return {};
+    }
+    return std::move(fileMap);
+}
+
+bool FileFilter::setPattern(const StringPiece& pattern) {
+    mPatternTokens = util::splitAndLowercase(pattern, ':');
+    return true;
+}
+
+bool FileFilter::operator()(const std::string& filename, FileType type) const {
+    if (filename == "." || filename == "..") {
+        return false;
+    }
+
+    const char kDir[] = "dir";
+    const char kFile[] = "file";
+    const size_t filenameLen = filename.length();
+    bool chatty = true;
+    for (const std::string& token : mPatternTokens) {
+        const char* tokenStr = token.c_str();
+        if (*tokenStr == '!') {
+            chatty = false;
+            tokenStr++;
+        }
+
+        if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
+            if (type != FileType::kDirectory) {
+                continue;
+            }
+            tokenStr += sizeof(kDir);
+        }
+
+        if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
+            if (type != FileType::kRegular) {
+                continue;
+            }
+            tokenStr += sizeof(kFile);
+        }
+
+        bool ignore = false;
+        size_t n = strlen(tokenStr);
+        if (*tokenStr == '*') {
+            // Math suffix.
+            tokenStr++;
+            n--;
+            if (n <= filenameLen) {
+                ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
+            }
+        } else if (n > 1 && tokenStr[n - 1] == '*') {
+            // Match prefix.
+            ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
+        } else {
+            ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
+        }
+
+        if (ignore) {
+            if (chatty) {
+                mDiag->warn(DiagMessage() << "skipping "
+                            << (type == FileType::kDirectory ? "dir '" : "file '")
+                            << filename << "' due to ignore pattern '"
+                            << token << "'");
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace file
+} // namespace aapt