diff options
Diffstat (limited to 'tools/aapt2/ResourceTable.cpp')
-rw-r--r-- | tools/aapt2/ResourceTable.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp new file mode 100644 index 0000000..0b3dd78 --- /dev/null +++ b/tools/aapt2/ResourceTable.cpp @@ -0,0 +1,334 @@ +/* + * 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 "Logger.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "Util.h" + +#include <algorithm> +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <string> +#include <tuple> + +namespace aapt { + +static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) { + return lhs.config < rhs; +} + +static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { + return lhs->type < rhs; +} + +static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) { + return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; +} + +ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) { +} + +std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) { + auto last = mTypes.end(); + auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType); + if (iter != last) { + if ((*iter)->type == type) { + return *iter; + } + } + return *mTypes.emplace(iter, new ResourceTableType{ type }); +} + +std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry( + std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) { + auto last = type->entries.end(); + auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry); + if (iter != last) { + if (name == (*iter)->name) { + return *iter; + } + } + return *type->entries.emplace(iter, new ResourceEntry{ name }); +} + +struct IsAttributeVisitor : ConstValueVisitor { + bool isAttribute = false; + + void visit(const Attribute&, ValueVisitorArgs&) override { + isAttribute = true; + } + + operator bool() { + return isAttribute; + } +}; + +/** + * The default handler for collisions. A return value of -1 means keep the + * existing value, 0 means fail, and +1 means take the incoming value. + */ +static int defaultCollisionHandler(const Value& existing, const Value& incoming) { + IsAttributeVisitor existingIsAttr, incomingIsAttr; + existing.accept(existingIsAttr, {}); + incoming.accept(incomingIsAttr, {}); + + if (!incomingIsAttr) { + if (incoming.isWeak()) { + // We're trying to add a weak resource but a resource + // already exists. Keep the existing. + return -1; + } else if (existing.isWeak()) { + // Override the weak resource with the new strong resource. + return 1; + } + // The existing and incoming values are strong, this is an error + // if the values are not both attributes. + return 0; + } + + if (!existingIsAttr) { + if (existing.isWeak()) { + // The existing value is not an attribute and it is weak, + // so take the incoming attribute value. + return 1; + } + // The existing value is not an attribute and it is strong, + // so the incoming attribute value is an error. + return 0; + } + + // + // Attribute specific handling. At this point we know both + // values are attributes. Since we can declare and define + // attributes all-over, we do special handling to see + // which definition sticks. + // + const Attribute& existingAttr = static_cast<const Attribute&>(existing); + const Attribute& incomingAttr = static_cast<const Attribute&>(incoming); + if (existingAttr.typeMask == incomingAttr.typeMask) { + // The two attributes are both DECLs, but they are plain attributes + // with the same formats. + // Keep the strongest one. + return existingAttr.isWeak() ? 1 : -1; + } + + if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) { + // Any incoming attribute is better than this. + return 1; + } + + if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) { + // The incoming attribute may be a USE instead of a DECL. + // Keep the existing attribute. + return -1; + } + return 0; +} + +static constexpr const char16_t* kValidNameChars = u"._-"; + +bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId, + const ConfigDescription& config, const SourceLine& source, + std::unique_ptr<Value> value) { + if (!name.package.empty() && name.package != mPackage) { + Logger::error(source) + << "resource '" + << name + << "' has incompatible package. Must be '" + << mPackage + << "'." + << std::endl; + return false; + } + + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars); + if (badCharIter != name.entry.end()) { + Logger::error(source) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << *badCharIter + << "'." + << std::endl; + return false; + } + + std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); + if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && + type->typeId != resId.typeId()) { + Logger::error(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but type '" + << type->type + << "' already has ID " + << std::hex << type->typeId << std::dec + << "." + << std::endl; + return false; + } + + std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); + if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && + entry->entryId != resId.entryId()) { + Logger::error(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but resource already has ID " + << ResourceId(mPackageId, type->typeId, entry->entryId) + << "." + << std::endl; + return false; + } + + const auto endIter = std::end(entry->values); + auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs); + if (iter == endIter || iter->config != config) { + // This resource did not exist before, add it. + entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) }); + } else { + int collisionResult = defaultCollisionHandler(*iter->value, *value); + if (collisionResult > 0) { + // Take the incoming value. + *iter = ResourceConfigValue{ config, source, {}, std::move(value) }; + } else if (collisionResult == 0) { + Logger::error(source) + << "duplicate value for resource '" << name << "' " + << "with config '" << iter->config << "'." + << std::endl; + + Logger::error(iter->source) + << "resource previously defined here." + << std::endl; + return false; + } + } + + if (resId.isValid()) { + type->typeId = resId.typeId(); + entry->entryId = resId.entryId(); + } + return true; +} + +bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, + const SourceLine& source, std::unique_ptr<Value> value) { + return addResource(name, ResourceId{}, config, source, std::move(value)); +} + +bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId, + const SourceLine& source) { + if (!name.package.empty() && name.package != mPackage) { + Logger::error(source) + << "resource '" + << name + << "' has incompatible package. Must be '" + << mPackage + << "'." + << std::endl; + return false; + } + + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars); + if (badCharIter != name.entry.end()) { + Logger::error(source) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << *badCharIter + << "'." + << std::endl; + return false; + } + + std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); + if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && + type->typeId != resId.typeId()) { + Logger::error(source) + << "trying to make resource '" + << name + << "' public with ID " + << resId + << " but type '" + << type->type + << "' already has ID " + << std::hex << type->typeId << std::dec + << "." + << std::endl; + return false; + } + + std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); + if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && + entry->entryId != resId.entryId()) { + Logger::error(source) + << "trying to make resource '" + << name + << "' public with ID " + << resId + << " but resource already has ID " + << ResourceId(mPackageId, type->typeId, entry->entryId) + << "." + << std::endl; + return false; + } + + type->publicStatus.isPublic = true; + entry->publicStatus.isPublic = true; + + if (resId.isValid()) { + type->typeId = resId.typeId(); + entry->entryId = resId.entryId(); + } + + if (entry->values.empty()) { + entry->values.push_back(ResourceConfigValue{ {}, source, {}, + util::make_unique<Sentinel>() }); + } + return true; +} + +std::tuple<const ResourceTableType*, const ResourceEntry*> +ResourceTable::findResource(const ResourceNameRef& name) const { + if (name.package != mPackage) { + return {nullptr, nullptr}; + } + + auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType); + if (iter == mTypes.end() || (*iter)->type != name.type) { + return {nullptr, nullptr}; + } + + const std::unique_ptr<ResourceTableType>& type = *iter; + auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry, + lessThanEntry); + if (iter2 == type->entries.end() || name.entry != (*iter2)->name) { + return {nullptr, nullptr}; + } + return {iter->get(), iter2->get()}; +} + +} // namespace aapt |