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/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
new file mode 100644
index 0000000..0db1c37
--- /dev/null
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -0,0 +1,481 @@
+/*
+ * 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 "ResourceUtils.h"
+#include "util/Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <sstream>
+
+namespace aapt {
+namespace ResourceUtils {
+
+void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+                         StringPiece16* outType, StringPiece16* outEntry) {
+    const char16_t* start = str.data();
+    const char16_t* end = start + str.size();
+    const char16_t* current = start;
+    while (current != end) {
+        if (outType->size() == 0 && *current == u'/') {
+            outType->assign(start, current - start);
+            start = current + 1;
+        } else if (outPackage->size() == 0 && *current == u':') {
+            outPackage->assign(start, current - start);
+            start = current + 1;
+        }
+        current++;
+    }
+    outEntry->assign(start, end - start);
+}
+
+bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
+                       bool* outPrivate) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    bool create = false;
+    bool priv = false;
+    if (trimmedStr.data()[0] == u'@') {
+        size_t offset = 1;
+        if (trimmedStr.data()[1] == u'+') {
+            create = true;
+            offset += 1;
+        } else if (trimmedStr.data()[1] == u'*') {
+            priv = true;
+            offset += 1;
+        }
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
+                            &entry);
+
+        const ResourceType* parsedType = parseResourceType(type);
+        if (!parsedType) {
+            return false;
+        }
+
+        if (create && *parsedType != ResourceType::kId) {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = *parsedType;
+        outRef->entry = entry;
+        if (outCreate) {
+            *outCreate = create;
+        }
+        if (outPrivate) {
+            *outPrivate = priv;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    if (trimmedStr.empty()) {
+        return false;
+    }
+
+    if (*trimmedStr.data() == u'?') {
+        StringPiece16 package;
+        StringPiece16 type;
+        StringPiece16 entry;
+        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+
+        if (!type.empty() && type != u"attr") {
+            return false;
+        }
+
+        outRef->package = package;
+        outRef->type = ResourceType::kAttr;
+        outRef->entry = entry;
+        return true;
+    }
+    return false;
+}
+
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
+    if (str.empty()) {
+        return {};
+    }
+
+    StringPiece16 name = str;
+
+    bool hasLeadingIdentifiers = false;
+    bool privateRef = false;
+
+    // Skip over these identifiers. A style's parent is a normal reference.
+    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+        hasLeadingIdentifiers = true;
+        name = name.substr(1, name.size() - 1);
+        if (name.data()[0] == u'*') {
+            privateRef = true;
+            name = name.substr(1, name.size() - 1);
+        }
+    }
+
+    ResourceNameRef ref;
+    ref.type = ResourceType::kStyle;
+
+    StringPiece16 typeStr;
+    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+    if (!typeStr.empty()) {
+        // If we have a type, make sure it is a Style.
+        const ResourceType* parsedType = parseResourceType(typeStr);
+        if (!parsedType || *parsedType != ResourceType::kStyle) {
+            std::stringstream err;
+            err << "invalid resource type '" << typeStr << "' for parent of style";
+            *outError = err.str();
+            return {};
+        }
+    } else {
+        // No type was defined, this should not have a leading identifier.
+        if (hasLeadingIdentifiers) {
+            std::stringstream err;
+            err << "invalid parent reference '" << str << "'";
+            *outError = err.str();
+            return {};
+        }
+    }
+
+    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+        std::stringstream err;
+        err << "invalid parent reference '" << str << "'";
+        *outError = err.str();
+        return {};
+    }
+
+    Reference result(ref);
+    result.privateReference = privateRef;
+    return result;
+}
+
+std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
+    ResourceNameRef ref;
+    bool privateRef = false;
+    if (tryParseReference(str, &ref, outCreate, &privateRef)) {
+        std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
+        value->privateReference = privateRef;
+        return value;
+    }
+
+    if (tryParseAttributeReference(str, &ref)) {
+        if (outCreate) {
+            *outCreate = false;
+        }
+        return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    android::Res_value value = { };
+    if (trimmedStr == u"@null") {
+        // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
+        // Instead we set the data type to TYPE_REFERENCE with a value of 0.
+        value.dataType = android::Res_value::TYPE_REFERENCE;
+    } else if (trimmedStr == u"@empty") {
+        // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
+        value.dataType = android::Res_value::TYPE_NULL;
+        value.data = android::Res_value::DATA_NULL_EMPTY;
+    } else {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
+                                                    const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    for (const Attribute::Symbol& symbol : enumAttr->symbols) {
+        // Enum symbols are stored as @package:id/symbol resources,
+        // so we need to match against the 'entry' part of the identifier.
+        const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
+        if (trimmedStr == enumSymbolResourceName.entry) {
+            android::Res_value value = { };
+            value.dataType = android::Res_value::TYPE_INT_DEC;
+            value.data = symbol.value;
+            return util::make_unique<BinaryPrimitive>(value);
+        }
+    }
+    return {};
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
+                                                    const StringPiece16& str) {
+    android::Res_value flags = { };
+    flags.dataType = android::Res_value::TYPE_INT_DEC;
+
+    for (StringPiece16 part : util::tokenize(str, u'|')) {
+        StringPiece16 trimmedPart = util::trimWhitespace(part);
+
+        bool flagSet = false;
+        for (const Attribute::Symbol& symbol : flagAttr->symbols) {
+            // Flag symbols are stored as @package:id/symbol resources,
+            // so we need to match against the 'entry' part of the identifier.
+            const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
+            if (trimmedPart == flagSymbolResourceName.entry) {
+                flags.data |= symbol.value;
+                flagSet = true;
+                break;
+            }
+        }
+
+        if (!flagSet) {
+            return {};
+        }
+    }
+    return util::make_unique<BinaryPrimitive>(flags);
+}
+
+static uint32_t parseHex(char16_t c, bool* outError) {
+    if (c >= u'0' && c <= u'9') {
+        return c - u'0';
+    } else if (c >= u'a' && c <= u'f') {
+        return c - u'a' + 0xa;
+    } else if (c >= u'A' && c <= u'F') {
+        return c - u'A' + 0xa;
+    } else {
+        *outError = true;
+        return 0xffffffffu;
+    }
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
+    StringPiece16 colorStr(util::trimWhitespace(str));
+    const char16_t* start = colorStr.data();
+    const size_t len = colorStr.size();
+    if (len == 0 || start[0] != u'#') {
+        return {};
+    }
+
+    android::Res_value value = { };
+    bool error = false;
+    if (len == 4) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[1], &error) << 16;
+        value.data |= parseHex(start[2], &error) << 12;
+        value.data |= parseHex(start[2], &error) << 8;
+        value.data |= parseHex(start[3], &error) << 4;
+        value.data |= parseHex(start[3], &error);
+    } else if (len == 5) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[1], &error) << 24;
+        value.data |= parseHex(start[2], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[3], &error) << 8;
+        value.data |= parseHex(start[4], &error) << 4;
+        value.data |= parseHex(start[4], &error);
+    } else if (len == 7) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
+        value.data = 0xff000000u;
+        value.data |= parseHex(start[1], &error) << 20;
+        value.data |= parseHex(start[2], &error) << 16;
+        value.data |= parseHex(start[3], &error) << 12;
+        value.data |= parseHex(start[4], &error) << 8;
+        value.data |= parseHex(start[5], &error) << 4;
+        value.data |= parseHex(start[6], &error);
+    } else if (len == 9) {
+        value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
+        value.data |= parseHex(start[1], &error) << 28;
+        value.data |= parseHex(start[2], &error) << 24;
+        value.data |= parseHex(start[3], &error) << 20;
+        value.data |= parseHex(start[4], &error) << 16;
+        value.data |= parseHex(start[5], &error) << 12;
+        value.data |= parseHex(start[6], &error) << 8;
+        value.data |= parseHex(start[7], &error) << 4;
+        value.data |= parseHex(start[8], &error);
+    } else {
+        return {};
+    }
+    return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+    StringPiece16 trimmedStr(util::trimWhitespace(str));
+    uint32_t data = 0;
+    if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
+        data = 0xffffffffu;
+    } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
+        return {};
+    }
+    android::Res_value value = { };
+    value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+    value.data = data;
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
+    android::Res_value value;
+    if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
+        return {};
+    }
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
+uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
+    switch (type) {
+    case android::Res_value::TYPE_NULL:
+    case android::Res_value::TYPE_REFERENCE:
+    case android::Res_value::TYPE_ATTRIBUTE:
+    case android::Res_value::TYPE_DYNAMIC_REFERENCE:
+        return android::ResTable_map::TYPE_REFERENCE;
+
+    case android::Res_value::TYPE_STRING:
+        return android::ResTable_map::TYPE_STRING;
+
+    case android::Res_value::TYPE_FLOAT:
+        return android::ResTable_map::TYPE_FLOAT;
+
+    case android::Res_value::TYPE_DIMENSION:
+        return android::ResTable_map::TYPE_DIMENSION;
+
+    case android::Res_value::TYPE_FRACTION:
+        return android::ResTable_map::TYPE_FRACTION;
+
+    case android::Res_value::TYPE_INT_DEC:
+    case android::Res_value::TYPE_INT_HEX:
+        return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
+                | android::ResTable_map::TYPE_FLAGS;
+
+    case android::Res_value::TYPE_INT_BOOLEAN:
+        return android::ResTable_map::TYPE_BOOLEAN;
+
+    case android::Res_value::TYPE_INT_COLOR_ARGB8:
+    case android::Res_value::TYPE_INT_COLOR_RGB8:
+    case android::Res_value::TYPE_INT_COLOR_ARGB4:
+    case android::Res_value::TYPE_INT_COLOR_RGB4:
+        return android::ResTable_map::TYPE_COLOR;
+
+    default:
+        return 0;
+    };
+}
+
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& value, uint32_t typeMask,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
+    if (nullOrEmpty) {
+        return std::move(nullOrEmpty);
+    }
+
+    bool create = false;
+    std::unique_ptr<Reference> reference = tryParseReference(value, &create);
+    if (reference) {
+        if (create && onCreateReference) {
+            onCreateReference(reference->name.value());
+        }
+        return std::move(reference);
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_COLOR) {
+        // Try parsing this as a color.
+        std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
+        if (color) {
+            return std::move(color);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+        // Try parsing this as a boolean.
+        std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
+        if (boolean) {
+            return std::move(boolean);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+        // Try parsing this as an integer.
+        std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
+        if (integer) {
+            return std::move(integer);
+        }
+    }
+
+    const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
+            | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
+    if (typeMask & floatMask) {
+        // Try parsing this as a float.
+        std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
+        if (floatingPoint) {
+            if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
+                return std::move(floatingPoint);
+            }
+        }
+    }
+    return {};
+}
+
+/**
+ * We successively try to parse the string as a resource type that the Attribute
+ * allows.
+ */
+std::unique_ptr<Item> parseItemForAttribute(
+        const StringPiece16& str, const Attribute* attr,
+        std::function<void(const ResourceName&)> onCreateReference) {
+    const uint32_t typeMask = attr->typeMask;
+    std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
+    if (value) {
+        return value;
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_ENUM) {
+        // Try parsing this as an enum.
+        std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
+        if (enumValue) {
+            return std::move(enumValue);
+        }
+    }
+
+    if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+        // Try parsing this as a flag.
+        std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
+        if (flagValue) {
+            return std::move(flagValue);
+        }
+    }
+    return {};
+}
+
+} // namespace ResourceUtils
+} // namespace aapt