summaryrefslogtreecommitdiffstats
path: root/tools/aapt2/ResourceTable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/aapt2/ResourceTable.cpp')
-rw-r--r--tools/aapt2/ResourceTable.cpp334
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