Use Google3 style guide with .clang-format
Test: style change only, builds ok Change-Id: I885180e24cb2e7b58cfb4967c3bcb40058ce4078
This commit is contained in:
3
tools/aapt2/.clang-format
Normal file
3
tools/aapt2/.clang-format
Normal file
@@ -0,0 +1,3 @@
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 100
|
||||
|
||||
@@ -28,27 +28,27 @@ namespace aapt {
|
||||
* will come from the app's AndroidManifest.
|
||||
*/
|
||||
struct AppInfo {
|
||||
/**
|
||||
* App's package name.
|
||||
*/
|
||||
std::string package;
|
||||
/**
|
||||
* App's package name.
|
||||
*/
|
||||
std::string package;
|
||||
|
||||
/**
|
||||
* The App's minimum SDK version.
|
||||
*/
|
||||
Maybe<std::string> minSdkVersion;
|
||||
/**
|
||||
* The App's minimum SDK version.
|
||||
*/
|
||||
Maybe<std::string> minSdkVersion;
|
||||
|
||||
/**
|
||||
* The Version code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> versionCode;
|
||||
/**
|
||||
* The Version code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> versionCode;
|
||||
|
||||
/**
|
||||
* The revision code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> revisionCode;
|
||||
/**
|
||||
* The revision code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> revisionCode;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_APP_INFO_H
|
||||
#endif // AAPT_APP_INFO_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,148 +29,152 @@ namespace aapt {
|
||||
* initialization and comparison methods.
|
||||
*/
|
||||
struct ConfigDescription : public android::ResTable_config {
|
||||
/**
|
||||
* Returns an immutable default config.
|
||||
*/
|
||||
static const ConfigDescription& defaultConfig();
|
||||
/**
|
||||
* Returns an immutable default config.
|
||||
*/
|
||||
static const ConfigDescription& defaultConfig();
|
||||
|
||||
/*
|
||||
* Parse a string of the form 'fr-sw600dp-land' and fill in the
|
||||
* given ResTable_config with resulting configuration parameters.
|
||||
*
|
||||
* The resulting configuration has the appropriate sdkVersion defined
|
||||
* for backwards compatibility.
|
||||
*/
|
||||
static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
|
||||
/*
|
||||
* Parse a string of the form 'fr-sw600dp-land' and fill in the
|
||||
* given ResTable_config with resulting configuration parameters.
|
||||
*
|
||||
* The resulting configuration has the appropriate sdkVersion defined
|
||||
* for backwards compatibility.
|
||||
*/
|
||||
static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
|
||||
|
||||
/**
|
||||
* If the configuration uses an axis that was added after
|
||||
* the original Android release, make sure the SDK version
|
||||
* is set accordingly.
|
||||
*/
|
||||
static void applyVersionForCompatibility(ConfigDescription* config);
|
||||
/**
|
||||
* If the configuration uses an axis that was added after
|
||||
* the original Android release, make sure the SDK version
|
||||
* is set accordingly.
|
||||
*/
|
||||
static void applyVersionForCompatibility(ConfigDescription* config);
|
||||
|
||||
ConfigDescription();
|
||||
ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
|
||||
ConfigDescription(const ConfigDescription& o);
|
||||
ConfigDescription(ConfigDescription&& o);
|
||||
ConfigDescription();
|
||||
ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
|
||||
ConfigDescription(const ConfigDescription& o);
|
||||
ConfigDescription(ConfigDescription&& o);
|
||||
|
||||
ConfigDescription& operator=(const android::ResTable_config& o);
|
||||
ConfigDescription& operator=(const ConfigDescription& o);
|
||||
ConfigDescription& operator=(ConfigDescription&& o);
|
||||
ConfigDescription& operator=(const android::ResTable_config& o);
|
||||
ConfigDescription& operator=(const ConfigDescription& o);
|
||||
ConfigDescription& operator=(ConfigDescription&& o);
|
||||
|
||||
ConfigDescription copyWithoutSdkVersion() const;
|
||||
ConfigDescription copyWithoutSdkVersion() const;
|
||||
|
||||
/**
|
||||
* A configuration X dominates another configuration Y, if X has at least the
|
||||
* precedence of Y and X is strictly more general than Y: for any type defined
|
||||
* by X, the same type is defined by Y with a value equal to or, in the case
|
||||
* of ranges, more specific than that of X.
|
||||
*
|
||||
* For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
|
||||
* does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
|
||||
*/
|
||||
bool dominates(const ConfigDescription& o) const;
|
||||
/**
|
||||
* A configuration X dominates another configuration Y, if X has at least the
|
||||
* precedence of Y and X is strictly more general than Y: for any type defined
|
||||
* by X, the same type is defined by Y with a value equal to or, in the case
|
||||
* of ranges, more specific than that of X.
|
||||
*
|
||||
* For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
|
||||
* does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
|
||||
*/
|
||||
bool dominates(const ConfigDescription& o) const;
|
||||
|
||||
/**
|
||||
* Returns true if this configuration defines a more important configuration
|
||||
* parameter than o. For example, "en" has higher precedence than "v23",
|
||||
* whereas "en" has the same precedence as "en-v23".
|
||||
*/
|
||||
bool hasHigherPrecedenceThan(const ConfigDescription& o) const;
|
||||
/**
|
||||
* Returns true if this configuration defines a more important configuration
|
||||
* parameter than o. For example, "en" has higher precedence than "v23",
|
||||
* whereas "en" has the same precedence as "en-v23".
|
||||
*/
|
||||
bool hasHigherPrecedenceThan(const ConfigDescription& o) const;
|
||||
|
||||
/**
|
||||
* A configuration conflicts with another configuration if both
|
||||
* configurations define an incompatible configuration parameter. An
|
||||
* incompatible configuration parameter is a non-range, non-density parameter
|
||||
* that is defined in both configurations as a different, non-default value.
|
||||
*/
|
||||
bool conflictsWith(const ConfigDescription& o) const;
|
||||
/**
|
||||
* A configuration conflicts with another configuration if both
|
||||
* configurations define an incompatible configuration parameter. An
|
||||
* incompatible configuration parameter is a non-range, non-density parameter
|
||||
* that is defined in both configurations as a different, non-default value.
|
||||
*/
|
||||
bool conflictsWith(const ConfigDescription& o) const;
|
||||
|
||||
/**
|
||||
* A configuration is compatible with another configuration if both
|
||||
* configurations can match a common concrete device configuration and are
|
||||
* unrelated by domination. For example, land-v11 conflicts with port-v21
|
||||
* but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
|
||||
*/
|
||||
bool isCompatibleWith(const ConfigDescription& o) const;
|
||||
/**
|
||||
* A configuration is compatible with another configuration if both
|
||||
* configurations can match a common concrete device configuration and are
|
||||
* unrelated by domination. For example, land-v11 conflicts with port-v21
|
||||
* but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
|
||||
*/
|
||||
bool isCompatibleWith(const ConfigDescription& o) const;
|
||||
|
||||
bool matchWithDensity(const ConfigDescription& o) const;
|
||||
bool matchWithDensity(const ConfigDescription& o) const;
|
||||
|
||||
bool operator<(const ConfigDescription& o) const;
|
||||
bool operator<=(const ConfigDescription& o) const;
|
||||
bool operator==(const ConfigDescription& o) const;
|
||||
bool operator!=(const ConfigDescription& o) const;
|
||||
bool operator>=(const ConfigDescription& o) const;
|
||||
bool operator>(const ConfigDescription& o) const;
|
||||
bool operator<(const ConfigDescription& o) const;
|
||||
bool operator<=(const ConfigDescription& o) const;
|
||||
bool operator==(const ConfigDescription& o) const;
|
||||
bool operator!=(const ConfigDescription& o) const;
|
||||
bool operator>=(const ConfigDescription& o) const;
|
||||
bool operator>(const ConfigDescription& o) const;
|
||||
};
|
||||
|
||||
inline ConfigDescription::ConfigDescription() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
size = sizeof(android::ResTable_config);
|
||||
memset(this, 0, sizeof(*this));
|
||||
size = sizeof(android::ResTable_config);
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
|
||||
*this = o;
|
||||
*this = o;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
return *this;
|
||||
inline ConfigDescription& ConfigDescription::operator=(
|
||||
const android::ResTable_config& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
return *this;
|
||||
inline ConfigDescription& ConfigDescription::operator=(
|
||||
const ConfigDescription& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
|
||||
*this = o;
|
||||
return *this;
|
||||
*this = o;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::matchWithDensity(const ConfigDescription& o) const {
|
||||
return match(o) && (density == 0 || density == o.density);
|
||||
inline bool ConfigDescription::matchWithDensity(
|
||||
const ConfigDescription& o) const {
|
||||
return match(o) && (density == 0 || density == o.density);
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
|
||||
return compare(o) < 0;
|
||||
return compare(o) < 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
|
||||
return compare(o) <= 0;
|
||||
return compare(o) <= 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
|
||||
return compare(o) == 0;
|
||||
return compare(o) == 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
|
||||
return compare(o) != 0;
|
||||
return compare(o) != 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
|
||||
return compare(o) >= 0;
|
||||
return compare(o) >= 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
|
||||
return compare(o) > 0;
|
||||
return compare(o) > 0;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
|
||||
return out << o.toString().string();
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ConfigDescription& o) {
|
||||
return out << o.toString().string();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_CONFIG_DESCRIPTION_H
|
||||
#endif // AAPT_CONFIG_DESCRIPTION_H
|
||||
|
||||
@@ -23,76 +23,79 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestParse(const StringPiece& input,
|
||||
ConfigDescription* config = nullptr) {
|
||||
if (ConfigDescription::parse(input, config)) {
|
||||
return ::testing::AssertionSuccess() << input << " was successfully parsed";
|
||||
}
|
||||
return ::testing::AssertionFailure() << input << " could not be parsed";
|
||||
static ::testing::AssertionResult TestParse(
|
||||
const StringPiece& input, ConfigDescription* config = nullptr) {
|
||||
if (ConfigDescription::parse(input, config)) {
|
||||
return ::testing::AssertionSuccess() << input << " was successfully parsed";
|
||||
}
|
||||
return ::testing::AssertionFailure() << input << " could not be parsed";
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
|
||||
EXPECT_FALSE(TestParse("land-en"));
|
||||
EXPECT_FALSE(TestParse("hdpi-320dpi"));
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
|
||||
EXPECT_FALSE(TestParse("land-en"));
|
||||
EXPECT_FALSE(TestParse("hdpi-320dpi"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-land-"));
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-land-"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("", &config));
|
||||
EXPECT_EQ(std::string(""), config.toString().string());
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("", &config));
|
||||
EXPECT_EQ(std::string(""), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("fr-land", &config));
|
||||
EXPECT_EQ(std::string("fr-land"), config.toString().string());
|
||||
EXPECT_TRUE(TestParse("fr-land", &config));
|
||||
EXPECT_EQ(std::string("fr-land"), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
|
||||
EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string());
|
||||
EXPECT_TRUE(
|
||||
TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav",
|
||||
&config));
|
||||
EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav-v13"),
|
||||
config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseLocales) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("en-rUS", &config));
|
||||
EXPECT_EQ(std::string("en-rUS"), config.toString().string());
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("en-rUS", &config));
|
||||
EXPECT_EQ(std::string("en-rUS"), config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("sw600dp", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("sw600dp", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("sw600dp-v8", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
EXPECT_TRUE(TestParse("sw600dp-v8", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseCarAttribute) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("car", &config));
|
||||
EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("car", &config));
|
||||
EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("round", &config));
|
||||
EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
|
||||
config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
|
||||
EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
|
||||
EXPECT_EQ(std::string("round-v23"), config.toString().string());
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("round", &config));
|
||||
EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
|
||||
config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
|
||||
EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
|
||||
EXPECT_EQ(std::string("round-v23"), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("notround", &config));
|
||||
EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
|
||||
config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
|
||||
EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
|
||||
EXPECT_EQ(std::string("notround-v23"), config.toString().string());
|
||||
EXPECT_TRUE(TestParse("notround", &config));
|
||||
EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
|
||||
config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
|
||||
EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
|
||||
EXPECT_EQ(std::string("notround-v23"), config.toString().string());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "Debug.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "util/Util.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -31,275 +31,279 @@
|
||||
namespace aapt {
|
||||
|
||||
class PrintVisitor : public ValueVisitor {
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
std::cout << "(attr) type=";
|
||||
attr->printMask(&std::cout);
|
||||
static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
|
||||
android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr->typeMask & kMask) {
|
||||
for (const auto& symbol : attr->symbols) {
|
||||
std::cout << "\n " << symbol.symbol.name.value().entry;
|
||||
if (symbol.symbol.id) {
|
||||
std::cout << " (" << symbol.symbol.id.value() << ")";
|
||||
}
|
||||
std::cout << " = " << symbol.value;
|
||||
}
|
||||
void visit(Attribute* attr) override {
|
||||
std::cout << "(attr) type=";
|
||||
attr->printMask(&std::cout);
|
||||
static constexpr uint32_t kMask =
|
||||
android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr->typeMask & kMask) {
|
||||
for (const auto& symbol : attr->symbols) {
|
||||
std::cout << "\n " << symbol.symbol.name.value().entry;
|
||||
if (symbol.symbol.id) {
|
||||
std::cout << " (" << symbol.symbol.id.value() << ")";
|
||||
}
|
||||
std::cout << " = " << symbol.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
std::cout << "(style)";
|
||||
if (style->parent) {
|
||||
const Reference& parentRef = style->parent.value();
|
||||
std::cout << " parent=";
|
||||
if (parentRef.name) {
|
||||
if (parentRef.privateReference) {
|
||||
std::cout << "*";
|
||||
}
|
||||
std::cout << parentRef.name.value() << " ";
|
||||
}
|
||||
|
||||
if (parentRef.id) {
|
||||
std::cout << parentRef.id.value();
|
||||
}
|
||||
void visit(Style* style) override {
|
||||
std::cout << "(style)";
|
||||
if (style->parent) {
|
||||
const Reference& parentRef = style->parent.value();
|
||||
std::cout << " parent=";
|
||||
if (parentRef.name) {
|
||||
if (parentRef.privateReference) {
|
||||
std::cout << "*";
|
||||
}
|
||||
std::cout << parentRef.name.value() << " ";
|
||||
}
|
||||
|
||||
for (const auto& entry : style->entries) {
|
||||
std::cout << "\n ";
|
||||
if (entry.key.name) {
|
||||
const ResourceName& name = entry.key.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
if (parentRef.id) {
|
||||
std::cout << parentRef.id.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.key.id) {
|
||||
std::cout << "(" << entry.key.id.value() << ")";
|
||||
}
|
||||
|
||||
std::cout << "=" << *entry.value;
|
||||
for (const auto& entry : style->entries) {
|
||||
std::cout << "\n ";
|
||||
if (entry.key.name) {
|
||||
const ResourceName& name = entry.key.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
|
||||
if (entry.key.id) {
|
||||
std::cout << "(" << entry.key.id.value() << ")";
|
||||
}
|
||||
|
||||
std::cout << "=" << *entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Array* array) override {
|
||||
array->print(&std::cout);
|
||||
}
|
||||
void visit(Array* array) override { array->print(&std::cout); }
|
||||
|
||||
void visit(Plural* plural) override {
|
||||
plural->print(&std::cout);
|
||||
}
|
||||
void visit(Plural* plural) override { plural->print(&std::cout); }
|
||||
|
||||
void visit(Styleable* styleable) override {
|
||||
std::cout << "(styleable)";
|
||||
for (const auto& attr : styleable->entries) {
|
||||
std::cout << "\n ";
|
||||
if (attr.name) {
|
||||
const ResourceName& name = attr.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
|
||||
if (attr.id) {
|
||||
std::cout << "(" << attr.id.value() << ")";
|
||||
}
|
||||
void visit(Styleable* styleable) override {
|
||||
std::cout << "(styleable)";
|
||||
for (const auto& attr : styleable->entries) {
|
||||
std::cout << "\n ";
|
||||
if (attr.name) {
|
||||
const ResourceName& name = attr.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
|
||||
void visitItem(Item* item) override {
|
||||
item->print(&std::cout);
|
||||
if (attr.id) {
|
||||
std::cout << "(" << attr.id.value() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitItem(Item* item) override { item->print(&std::cout); }
|
||||
};
|
||||
|
||||
void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
|
||||
PrintVisitor visitor;
|
||||
void Debug::printTable(ResourceTable* table,
|
||||
const DebugPrintTableOptions& options) {
|
||||
PrintVisitor visitor;
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::cout << "Package name=" << package->name;
|
||||
if (package->id) {
|
||||
std::cout << " id=" << std::hex << (int) package->id.value() << std::dec;
|
||||
for (auto& package : table->packages) {
|
||||
std::cout << "Package name=" << package->name;
|
||||
if (package->id) {
|
||||
std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& type : package->types) {
|
||||
std::cout << "\n type " << type->type;
|
||||
if (type->id) {
|
||||
std::cout << " id=" << std::hex << (int)type->id.value() << std::dec;
|
||||
}
|
||||
std::cout << " entryCount=" << type->entries.size() << std::endl;
|
||||
|
||||
std::vector<const ResourceEntry*> sortedEntries;
|
||||
for (const auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(
|
||||
sortedEntries.begin(), sortedEntries.end(), entry.get(),
|
||||
[](const ResourceEntry* a, const ResourceEntry* b) -> bool {
|
||||
if (a->id && b->id) {
|
||||
return a->id.value() < b->id.value();
|
||||
} else if (a->id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
sortedEntries.insert(iter, entry.get());
|
||||
}
|
||||
|
||||
for (const ResourceEntry* entry : sortedEntries) {
|
||||
ResourceId id(package->id ? package->id.value() : uint8_t(0),
|
||||
type->id ? type->id.value() : uint8_t(0),
|
||||
entry->id ? entry->id.value() : uint16_t(0));
|
||||
ResourceName name(package->name, type->type, entry->name);
|
||||
|
||||
std::cout << " spec resource " << id << " " << name;
|
||||
switch (entry->symbolStatus.state) {
|
||||
case SymbolState::kPublic:
|
||||
std::cout << " PUBLIC";
|
||||
break;
|
||||
case SymbolState::kPrivate:
|
||||
std::cout << " _PRIVATE_";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& type : package->types) {
|
||||
std::cout << "\n type " << type->type;
|
||||
if (type->id) {
|
||||
std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
|
||||
}
|
||||
std::cout << " entryCount=" << type->entries.size() << std::endl;
|
||||
|
||||
std::vector<const ResourceEntry*> sortedEntries;
|
||||
for (const auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(),
|
||||
[](const ResourceEntry* a, const ResourceEntry* b) -> bool {
|
||||
if (a->id && b->id) {
|
||||
return a->id.value() < b->id.value();
|
||||
} else if (a->id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
sortedEntries.insert(iter, entry.get());
|
||||
}
|
||||
|
||||
for (const ResourceEntry* entry : sortedEntries) {
|
||||
ResourceId id(package->id ? package->id.value() : uint8_t(0),
|
||||
type->id ? type->id.value() : uint8_t(0),
|
||||
entry->id ? entry->id.value() : uint16_t(0));
|
||||
ResourceName name(package->name, type->type, entry->name);
|
||||
|
||||
std::cout << " spec resource " << id << " " << name;
|
||||
switch (entry->symbolStatus.state) {
|
||||
case SymbolState::kPublic: std::cout << " PUBLIC"; break;
|
||||
case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value->config << ") ";
|
||||
value->value->accept(&visitor);
|
||||
if (options.showSources && !value->value->getSource().path.empty()) {
|
||||
std::cout << " src=" << value->value->getSource();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value->config << ") ";
|
||||
value->value->accept(&visitor);
|
||||
if (options.showSources && !value->value->getSource().path.empty()) {
|
||||
std::cout << " src=" << value->value->getSource();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) {
|
||||
auto iter = std::lower_bound(names.begin(), names.end(), name);
|
||||
assert(iter != names.end() && *iter == name);
|
||||
return std::distance(names.begin(), iter);
|
||||
static size_t getNodeIndex(const std::vector<ResourceName>& names,
|
||||
const ResourceName& name) {
|
||||
auto iter = std::lower_bound(names.begin(), names.end(), name);
|
||||
assert(iter != names.end() && *iter == name);
|
||||
return std::distance(names.begin(), iter);
|
||||
}
|
||||
|
||||
void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) {
|
||||
std::map<ResourceName, std::set<ResourceName>> graph;
|
||||
void Debug::printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle) {
|
||||
std::map<ResourceName, std::set<ResourceName>> graph;
|
||||
|
||||
std::queue<ResourceName> stylesToVisit;
|
||||
stylesToVisit.push(targetStyle);
|
||||
for (; !stylesToVisit.empty(); stylesToVisit.pop()) {
|
||||
const ResourceName& styleName = stylesToVisit.front();
|
||||
std::set<ResourceName>& parents = graph[styleName];
|
||||
if (!parents.empty()) {
|
||||
// We've already visited this style.
|
||||
continue;
|
||||
}
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
for (const auto& value : entry->values) {
|
||||
if (Style* style = valueCast<Style>(value->value.get())) {
|
||||
if (style->parent && style->parent.value().name) {
|
||||
parents.insert(style->parent.value().name.value());
|
||||
stylesToVisit.push(style->parent.value().name.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::queue<ResourceName> stylesToVisit;
|
||||
stylesToVisit.push(targetStyle);
|
||||
for (; !stylesToVisit.empty(); stylesToVisit.pop()) {
|
||||
const ResourceName& styleName = stylesToVisit.front();
|
||||
std::set<ResourceName>& parents = graph[styleName];
|
||||
if (!parents.empty()) {
|
||||
// We've already visited this style.
|
||||
continue;
|
||||
}
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
for (const auto& value : entry->values) {
|
||||
if (Style* style = valueCast<Style>(value->value.get())) {
|
||||
if (style->parent && style->parent.value().name) {
|
||||
parents.insert(style->parent.value().name.value());
|
||||
stylesToVisit.push(style->parent.value().name.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ResourceName> names;
|
||||
for (const auto& entry : graph) {
|
||||
names.push_back(entry.first);
|
||||
std::vector<ResourceName> names;
|
||||
for (const auto& entry : graph) {
|
||||
names.push_back(entry.first);
|
||||
}
|
||||
|
||||
std::cout << "digraph styles {\n";
|
||||
for (const auto& name : names) {
|
||||
std::cout << " node_" << getNodeIndex(names, name) << " [label=\"" << name
|
||||
<< "\"];\n";
|
||||
}
|
||||
|
||||
for (const auto& entry : graph) {
|
||||
const ResourceName& styleName = entry.first;
|
||||
size_t styleNodeIndex = getNodeIndex(names, styleName);
|
||||
|
||||
for (const auto& parentName : entry.second) {
|
||||
std::cout << " node_" << styleNodeIndex << " -> "
|
||||
<< "node_" << getNodeIndex(names, parentName) << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "digraph styles {\n";
|
||||
for (const auto& name : names) {
|
||||
std::cout << " node_" << getNodeIndex(names, name)
|
||||
<< " [label=\"" << name << "\"];\n";
|
||||
}
|
||||
|
||||
for (const auto& entry : graph) {
|
||||
const ResourceName& styleName = entry.first;
|
||||
size_t styleNodeIndex = getNodeIndex(names, styleName);
|
||||
|
||||
for (const auto& parentName : entry.second) {
|
||||
std::cout << " node_" << styleNodeIndex << " -> "
|
||||
<< "node_" << getNodeIndex(names, parentName) << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "}" << std::endl;
|
||||
std::cout << "}" << std::endl;
|
||||
}
|
||||
|
||||
void Debug::dumpHex(const void* data, size_t len) {
|
||||
const uint8_t* d = (const uint8_t*) data;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t) d[i] << " ";
|
||||
if (i % 8 == 7) {
|
||||
std::cerr << "\n";
|
||||
}
|
||||
const uint8_t* d = (const uint8_t*)data;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i]
|
||||
<< " ";
|
||||
if (i % 8 == 7) {
|
||||
std::cerr << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (len - 1 % 8 != 7) {
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
if (len - 1 % 8 != 7) {
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class XmlPrinter : public xml::Visitor {
|
||||
public:
|
||||
using xml::Visitor::visit;
|
||||
public:
|
||||
using xml::Visitor::visit;
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "E: ";
|
||||
if (!el->namespaceUri.empty()) {
|
||||
std::cerr << el->namespaceUri << ":";
|
||||
}
|
||||
std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
|
||||
void visit(xml::Element* el) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "E: ";
|
||||
if (!el->namespaceUri.empty()) {
|
||||
std::cerr << el->namespaceUri << ":";
|
||||
}
|
||||
std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
|
||||
|
||||
for (const xml::Attribute& attr : el->attributes) {
|
||||
std::cerr << mPrefix << " A: ";
|
||||
if (!attr.namespaceUri.empty()) {
|
||||
std::cerr << attr.namespaceUri << ":";
|
||||
}
|
||||
std::cerr << attr.name << "=" << attr.value << "\n";
|
||||
}
|
||||
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(el);
|
||||
mPrefix.resize(previousSize);
|
||||
for (const xml::Attribute& attr : el->attributes) {
|
||||
std::cerr << mPrefix << " A: ";
|
||||
if (!attr.namespaceUri.empty()) {
|
||||
std::cerr << attr.namespaceUri << ":";
|
||||
}
|
||||
std::cerr << attr.name << "=" << attr.value << "\n";
|
||||
}
|
||||
|
||||
void visit(xml::Namespace* ns) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
|
||||
<< " (line=" << ns->lineNumber << ")\n";
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(el);
|
||||
mPrefix.resize(previousSize);
|
||||
}
|
||||
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(ns);
|
||||
mPrefix.resize(previousSize);
|
||||
}
|
||||
void visit(xml::Namespace* ns) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
|
||||
<< " (line=" << ns->lineNumber << ")\n";
|
||||
|
||||
void visit(xml::Text* text) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "T: '" << text->text << "'\n";
|
||||
}
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(ns);
|
||||
mPrefix.resize(previousSize);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mPrefix;
|
||||
void visit(xml::Text* text) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "T: '" << text->text << "'\n";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mPrefix;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
void Debug::dumpXml(xml::XmlResource* doc) {
|
||||
XmlPrinter printer;
|
||||
doc->root->accept(&printer);
|
||||
XmlPrinter printer;
|
||||
doc->root->accept(&printer);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -27,17 +27,18 @@
|
||||
namespace aapt {
|
||||
|
||||
struct DebugPrintTableOptions {
|
||||
bool showSources = false;
|
||||
bool showSources = false;
|
||||
};
|
||||
|
||||
struct Debug {
|
||||
static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
|
||||
static void printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle);
|
||||
static void dumpHex(const void* data, size_t len);
|
||||
static void dumpXml(xml::XmlResource* doc);
|
||||
static void printTable(ResourceTable* table,
|
||||
const DebugPrintTableOptions& options = {});
|
||||
static void printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle);
|
||||
static void dumpHex(const void* data, size_t len);
|
||||
static void dumpXml(xml::XmlResource* doc);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_DEBUG_H
|
||||
#endif // AAPT_DEBUG_H
|
||||
|
||||
@@ -29,119 +29,112 @@
|
||||
namespace aapt {
|
||||
|
||||
struct DiagMessageActual {
|
||||
Source source;
|
||||
std::string message;
|
||||
Source source;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct DiagMessage {
|
||||
private:
|
||||
Source mSource;
|
||||
std::stringstream mMessage;
|
||||
private:
|
||||
Source mSource;
|
||||
std::stringstream mMessage;
|
||||
|
||||
public:
|
||||
DiagMessage() = default;
|
||||
public:
|
||||
DiagMessage() = default;
|
||||
|
||||
explicit DiagMessage(const StringPiece& src) : mSource(src) {
|
||||
}
|
||||
explicit DiagMessage(const StringPiece& src) : mSource(src) {}
|
||||
|
||||
explicit DiagMessage(const Source& src) : mSource(src) {
|
||||
}
|
||||
explicit DiagMessage(const Source& src) : mSource(src) {}
|
||||
|
||||
explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {
|
||||
}
|
||||
explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {}
|
||||
|
||||
template <typename T>
|
||||
DiagMessage& operator<<(const T& value) {
|
||||
mMessage << value;
|
||||
return *this;
|
||||
}
|
||||
template <typename T>
|
||||
DiagMessage& operator<<(const T& value) {
|
||||
mMessage << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DiagMessageActual build() const {
|
||||
return DiagMessageActual{ mSource, mMessage.str() };
|
||||
}
|
||||
DiagMessageActual build() const {
|
||||
return DiagMessageActual{mSource, mMessage.str()};
|
||||
}
|
||||
};
|
||||
|
||||
struct IDiagnostics {
|
||||
virtual ~IDiagnostics() = default;
|
||||
virtual ~IDiagnostics() = default;
|
||||
|
||||
enum class Level {
|
||||
Note,
|
||||
Warn,
|
||||
Error
|
||||
};
|
||||
enum class Level { Note, Warn, Error };
|
||||
|
||||
virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
|
||||
virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
|
||||
|
||||
virtual void error(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Error, actual);
|
||||
}
|
||||
virtual void error(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Error, actual);
|
||||
}
|
||||
|
||||
virtual void warn(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Warn, actual);
|
||||
}
|
||||
virtual void warn(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Warn, actual);
|
||||
}
|
||||
|
||||
virtual void note(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Note, actual);
|
||||
}
|
||||
virtual void note(const DiagMessage& message) {
|
||||
DiagMessageActual actual = message.build();
|
||||
log(Level::Note, actual);
|
||||
}
|
||||
};
|
||||
|
||||
class StdErrDiagnostics : public IDiagnostics {
|
||||
public:
|
||||
StdErrDiagnostics() = default;
|
||||
public:
|
||||
StdErrDiagnostics() = default;
|
||||
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
const char* tag;
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
const char* tag;
|
||||
|
||||
switch (level) {
|
||||
case Level::Error:
|
||||
mNumErrors++;
|
||||
if (mNumErrors > 20) {
|
||||
return;
|
||||
}
|
||||
tag = "error";
|
||||
break;
|
||||
|
||||
case Level::Warn:
|
||||
tag = "warn";
|
||||
break;
|
||||
|
||||
case Level::Note:
|
||||
tag = "note";
|
||||
break;
|
||||
switch (level) {
|
||||
case Level::Error:
|
||||
mNumErrors++;
|
||||
if (mNumErrors > 20) {
|
||||
return;
|
||||
}
|
||||
tag = "error";
|
||||
break;
|
||||
|
||||
if (!actualMsg.source.path.empty()) {
|
||||
std::cerr << actualMsg.source << ": ";
|
||||
}
|
||||
std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
|
||||
case Level::Warn:
|
||||
tag = "warn";
|
||||
break;
|
||||
|
||||
case Level::Note:
|
||||
tag = "note";
|
||||
break;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mNumErrors = 0;
|
||||
if (!actualMsg.source.path.empty()) {
|
||||
std::cerr << actualMsg.source << ": ";
|
||||
}
|
||||
std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
|
||||
private:
|
||||
size_t mNumErrors = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
|
||||
};
|
||||
|
||||
class SourcePathDiagnostics : public IDiagnostics {
|
||||
public:
|
||||
SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
|
||||
}
|
||||
public:
|
||||
SourcePathDiagnostics(const Source& src, IDiagnostics* diag)
|
||||
: mSource(src), mDiag(diag) {}
|
||||
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
actualMsg.source.path = mSource.path;
|
||||
mDiag->log(level, actualMsg);
|
||||
}
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
actualMsg.source.path = mSource.path;
|
||||
mDiag->log(level, actualMsg);
|
||||
}
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
IDiagnostics* mDiag;
|
||||
private:
|
||||
Source mSource;
|
||||
IDiagnostics* mDiag;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
|
||||
DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_DIAGNOSTICS_H */
|
||||
|
||||
@@ -14,76 +14,78 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "DominatorTree.h"
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
DominatorTree::DominatorTree(
|
||||
const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
|
||||
for (const auto& config : configs) {
|
||||
mProductRoots[config->product].tryAddChild(
|
||||
util::make_unique<Node>(config.get(), nullptr));
|
||||
}
|
||||
const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
|
||||
for (const auto& config : configs) {
|
||||
mProductRoots[config->product].tryAddChild(
|
||||
util::make_unique<Node>(config.get(), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
void DominatorTree::accept(Visitor* visitor) {
|
||||
for (auto& entry : mProductRoots) {
|
||||
visitor->visitTree(entry.first, &entry.second);
|
||||
}
|
||||
for (auto& entry : mProductRoots) {
|
||||
visitor->visitTree(entry.first, &entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
bool DominatorTree::Node::tryAddChild(std::unique_ptr<Node> newChild) {
|
||||
assert(newChild->mValue && "cannot add a root or empty node as a child");
|
||||
if (mValue && !dominates(newChild.get())) {
|
||||
// This is not the root and the child dominates us.
|
||||
return false;
|
||||
}
|
||||
return addChild(std::move(newChild));
|
||||
assert(newChild->mValue && "cannot add a root or empty node as a child");
|
||||
if (mValue && !dominates(newChild.get())) {
|
||||
// This is not the root and the child dominates us.
|
||||
return false;
|
||||
}
|
||||
return addChild(std::move(newChild));
|
||||
}
|
||||
|
||||
bool DominatorTree::Node::addChild(std::unique_ptr<Node> newChild) {
|
||||
bool hasDominatedChildren = false;
|
||||
// Demote children dominated by the new config.
|
||||
for (auto& child : mChildren) {
|
||||
if (newChild->dominates(child.get())) {
|
||||
child->mParent = newChild.get();
|
||||
newChild->mChildren.push_back(std::move(child));
|
||||
child = {};
|
||||
hasDominatedChildren = true;
|
||||
}
|
||||
bool hasDominatedChildren = false;
|
||||
// Demote children dominated by the new config.
|
||||
for (auto& child : mChildren) {
|
||||
if (newChild->dominates(child.get())) {
|
||||
child->mParent = newChild.get();
|
||||
newChild->mChildren.push_back(std::move(child));
|
||||
child = {};
|
||||
hasDominatedChildren = true;
|
||||
}
|
||||
// Remove dominated children.
|
||||
if (hasDominatedChildren) {
|
||||
mChildren.erase(std::remove_if(mChildren.begin(), mChildren.end(),
|
||||
[](const std::unique_ptr<Node>& child) -> bool {
|
||||
return child == nullptr;
|
||||
}), mChildren.end());
|
||||
}
|
||||
// Remove dominated children.
|
||||
if (hasDominatedChildren) {
|
||||
mChildren.erase(
|
||||
std::remove_if(mChildren.begin(), mChildren.end(),
|
||||
[](const std::unique_ptr<Node>& child) -> bool {
|
||||
return child == nullptr;
|
||||
}),
|
||||
mChildren.end());
|
||||
}
|
||||
// Add the new config to a child if a child dominates the new config.
|
||||
for (auto& child : mChildren) {
|
||||
if (child->dominates(newChild.get())) {
|
||||
child->addChild(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
// Add the new config to a child if a child dominates the new config.
|
||||
for (auto& child : mChildren) {
|
||||
if (child->dominates(newChild.get())) {
|
||||
child->addChild(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// The new config is not dominated by a child, so add it here.
|
||||
newChild->mParent = this;
|
||||
mChildren.push_back(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
// The new config is not dominated by a child, so add it here.
|
||||
newChild->mParent = this;
|
||||
mChildren.push_back(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DominatorTree::Node::dominates(const Node* other) const {
|
||||
// Check root node dominations.
|
||||
if (other->isRootNode()) {
|
||||
return isRootNode();
|
||||
} else if (isRootNode()) {
|
||||
return true;
|
||||
}
|
||||
// Neither node is a root node; compare the configurations.
|
||||
return mValue->config.dominates(other->mValue->config);
|
||||
// Check root node dominations.
|
||||
if (other->isRootNode()) {
|
||||
return isRootNode();
|
||||
} else if (isRootNode()) {
|
||||
return true;
|
||||
}
|
||||
// Neither node is a root node; compare the configurations.
|
||||
return mValue->config.dominates(other->mValue->config);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -46,82 +46,76 @@ namespace aapt {
|
||||
* will exhibit undefined behavior.
|
||||
*/
|
||||
class DominatorTree {
|
||||
public:
|
||||
explicit DominatorTree(const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
|
||||
public:
|
||||
explicit DominatorTree(
|
||||
const std::vector<std::unique_ptr<ResourceConfigValue>>& configs);
|
||||
|
||||
class Node {
|
||||
public:
|
||||
explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr) :
|
||||
mValue(value), mParent(parent) {
|
||||
}
|
||||
class Node {
|
||||
public:
|
||||
explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr)
|
||||
: mValue(value), mParent(parent) {}
|
||||
|
||||
inline ResourceConfigValue* value() const {
|
||||
return mValue;
|
||||
}
|
||||
inline ResourceConfigValue* value() const { return mValue; }
|
||||
|
||||
inline Node* parent() const {
|
||||
return mParent;
|
||||
}
|
||||
inline Node* parent() const { return mParent; }
|
||||
|
||||
inline bool isRootNode() const {
|
||||
return !mValue;
|
||||
}
|
||||
inline bool isRootNode() const { return !mValue; }
|
||||
|
||||
inline const std::vector<std::unique_ptr<Node>>& children() const {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
bool tryAddChild(std::unique_ptr<Node> newChild);
|
||||
|
||||
private:
|
||||
bool addChild(std::unique_ptr<Node> newChild);
|
||||
bool dominates(const Node* other) const;
|
||||
|
||||
ResourceConfigValue* mValue;
|
||||
Node* mParent;
|
||||
std::vector<std::unique_ptr<Node>> mChildren;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Node);
|
||||
};
|
||||
|
||||
struct Visitor {
|
||||
virtual ~Visitor() = default;
|
||||
virtual void visitTree(const std::string& product, Node* root) = 0;
|
||||
};
|
||||
|
||||
class BottomUpVisitor : public Visitor {
|
||||
public:
|
||||
virtual ~BottomUpVisitor() = default;
|
||||
|
||||
void visitTree(const std::string& product, Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void visitConfig(Node* node) = 0;
|
||||
|
||||
private:
|
||||
void visitNode(Node* node) {
|
||||
for (auto& child : node->children()) {
|
||||
visitNode(child.get());
|
||||
}
|
||||
visitConfig(node);
|
||||
}
|
||||
};
|
||||
|
||||
void accept(Visitor* visitor);
|
||||
|
||||
inline const std::map<std::string, Node>& getProductRoots() const {
|
||||
return mProductRoots;
|
||||
inline const std::vector<std::unique_ptr<Node>>& children() const {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DominatorTree);
|
||||
bool tryAddChild(std::unique_ptr<Node> newChild);
|
||||
|
||||
std::map<std::string, Node> mProductRoots;
|
||||
private:
|
||||
bool addChild(std::unique_ptr<Node> newChild);
|
||||
bool dominates(const Node* other) const;
|
||||
|
||||
ResourceConfigValue* mValue;
|
||||
Node* mParent;
|
||||
std::vector<std::unique_ptr<Node>> mChildren;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Node);
|
||||
};
|
||||
|
||||
struct Visitor {
|
||||
virtual ~Visitor() = default;
|
||||
virtual void visitTree(const std::string& product, Node* root) = 0;
|
||||
};
|
||||
|
||||
class BottomUpVisitor : public Visitor {
|
||||
public:
|
||||
virtual ~BottomUpVisitor() = default;
|
||||
|
||||
void visitTree(const std::string& product, Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void visitConfig(Node* node) = 0;
|
||||
|
||||
private:
|
||||
void visitNode(Node* node) {
|
||||
for (auto& child : node->children()) {
|
||||
visitNode(child.get());
|
||||
}
|
||||
visitConfig(node);
|
||||
}
|
||||
};
|
||||
|
||||
void accept(Visitor* visitor);
|
||||
|
||||
inline const std::map<std::string, Node>& getProductRoots() const {
|
||||
return mProductRoots;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DominatorTree);
|
||||
|
||||
std::map<std::string, Node> mProductRoots;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_DOMINATOR_TREE_H
|
||||
#endif // AAPT_DOMINATOR_TREE_H
|
||||
|
||||
@@ -27,125 +27,132 @@ namespace aapt {
|
||||
namespace {
|
||||
|
||||
class PrettyPrinter : public DominatorTree::Visitor {
|
||||
public:
|
||||
explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {
|
||||
}
|
||||
public:
|
||||
explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {}
|
||||
|
||||
void visitTree(const std::string& product, DominatorTree::Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get(), 0);
|
||||
}
|
||||
void visitTree(const std::string& product,
|
||||
DominatorTree::Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string toString(DominatorTree* tree) {
|
||||
mBuffer.str("");
|
||||
mBuffer.clear();
|
||||
tree->accept(this);
|
||||
return mBuffer.str();
|
||||
std::string toString(DominatorTree* tree) {
|
||||
mBuffer.str("");
|
||||
mBuffer.clear();
|
||||
tree->accept(this);
|
||||
return mBuffer.str();
|
||||
}
|
||||
|
||||
private:
|
||||
void visitConfig(const DominatorTree::Node* node, const int indent) {
|
||||
auto configString = node->value()->config.toString();
|
||||
mBuffer << std::string(indent, ' ')
|
||||
<< (configString.isEmpty() ? "<default>" : configString)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void visitNode(const DominatorTree::Node* node, const int indent) {
|
||||
visitConfig(node, indent);
|
||||
for (const auto& child : node->children()) {
|
||||
visitNode(child.get(), indent + mIndent);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void visitConfig(const DominatorTree::Node* node, const int indent) {
|
||||
auto configString = node->value()->config.toString();
|
||||
mBuffer << std::string(indent, ' ')
|
||||
<< (configString.isEmpty() ? "<default>" : configString)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void visitNode(const DominatorTree::Node* node, const int indent) {
|
||||
visitConfig(node, indent);
|
||||
for (const auto& child : node->children()) {
|
||||
visitNode(child.get(), indent + mIndent);
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream mBuffer;
|
||||
const int mIndent = 2;
|
||||
std::stringstream mBuffer;
|
||||
const int mIndent = 2;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
TEST(DominatorTreeTest, DefaultDominatesEverything) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13");
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig =
|
||||
test::parseConfigOrDie("sw600dp-land-v13");
|
||||
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" land\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" land\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
}
|
||||
|
||||
TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13");
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig =
|
||||
test::parseConfigOrDie("sw600dp-land-v13");
|
||||
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "phablet"));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet"));
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(defaultConfig, "phablet"));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet"));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" land\n"
|
||||
"<default>\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" land\n"
|
||||
"<default>\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
}
|
||||
|
||||
TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription enConfig = test::parseConfigOrDie("en");
|
||||
const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
|
||||
const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4");
|
||||
const ConfigDescription ldrtlXhdpiConfig = test::parseConfigOrDie("ldrtl-xhdpi-v4");
|
||||
const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13");
|
||||
const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14");
|
||||
const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14");
|
||||
const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13");
|
||||
const ConfigDescription v20Config = test::parseConfigOrDie("v20");
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription enConfig = test::parseConfigOrDie("en");
|
||||
const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21");
|
||||
const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4");
|
||||
const ConfigDescription ldrtlXhdpiConfig =
|
||||
test::parseConfigOrDie("ldrtl-xhdpi-v4");
|
||||
const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13");
|
||||
const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14");
|
||||
const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14");
|
||||
const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13");
|
||||
const ConfigDescription v20Config = test::parseConfigOrDie("v20");
|
||||
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, ""));
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> configs;
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, ""));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, ""));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" en\n"
|
||||
" en-v21\n"
|
||||
" ldrtl-v4\n"
|
||||
" ldrtl-xhdpi-v4\n"
|
||||
" sw300dp-v13\n"
|
||||
" sw540dp-v14\n"
|
||||
" sw600dp-v14\n"
|
||||
" sw720dp-v13\n"
|
||||
" v20\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
std::string expected =
|
||||
"<default>\n"
|
||||
" en\n"
|
||||
" en-v21\n"
|
||||
" ldrtl-v4\n"
|
||||
" ldrtl-xhdpi-v4\n"
|
||||
" sw300dp-v13\n"
|
||||
" sw540dp-v14\n"
|
||||
" sw600dp-v14\n"
|
||||
" sw720dp-v13\n"
|
||||
" v20\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -25,155 +25,167 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
Flags& Flags::requiredFlag(const StringPiece& name,
|
||||
const StringPiece& description, std::string* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false});
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, true, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description,
|
||||
Flags& Flags::requiredFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false });
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, true, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Flags& Flags::optionalFlag(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
Maybe<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->push_back(arg.toString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::unordered_set<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->insert(arg.toString());
|
||||
return true;
|
||||
};
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
value->insert(arg.toString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false });
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
bool* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = true;
|
||||
return true;
|
||||
};
|
||||
Flags& Flags::optionalSwitch(const StringPiece& name,
|
||||
const StringPiece& description, bool* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false });
|
||||
return *this;
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 0, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
constexpr size_t kWidth = 50;
|
||||
constexpr size_t kWidth = 50;
|
||||
|
||||
*out << command << " [options]";
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required) {
|
||||
*out << " " << flag.name << " arg";
|
||||
}
|
||||
*out << command << " [options]";
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required) {
|
||||
*out << " " << flag.name << " arg";
|
||||
}
|
||||
}
|
||||
|
||||
*out << " files...\n\nOptions:\n";
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
std::string argLine = flag.name;
|
||||
if (flag.numArgs > 0) {
|
||||
argLine += " arg";
|
||||
}
|
||||
|
||||
*out << " files...\n\nOptions:\n";
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
std::string argLine = flag.name;
|
||||
if (flag.numArgs > 0) {
|
||||
argLine += " arg";
|
||||
}
|
||||
|
||||
// Split the description by newlines and write out the argument (which is empty after
|
||||
// the first line) followed by the description line. This will make sure that multiline
|
||||
// descriptions are still right justified and aligned.
|
||||
for (StringPiece line : util::tokenize(flag.description, '\n')) {
|
||||
*out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
|
||||
argLine = " ";
|
||||
}
|
||||
// Split the description by newlines and write out the argument (which is
|
||||
// empty after
|
||||
// the first line) followed by the description line. This will make sure
|
||||
// that multiline
|
||||
// descriptions are still right justified and aligned.
|
||||
for (StringPiece line : util::tokenize(flag.description, '\n')) {
|
||||
*out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
|
||||
argLine = " ";
|
||||
}
|
||||
*out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n";
|
||||
out->flush();
|
||||
}
|
||||
*out << " " << std::setw(kWidth) << std::left << "-h"
|
||||
<< "Displays this help menu\n";
|
||||
out->flush();
|
||||
}
|
||||
|
||||
bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args,
|
||||
bool Flags::parse(const StringPiece& command,
|
||||
const std::vector<StringPiece>& args,
|
||||
std::ostream* outError) {
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
StringPiece arg = args[i];
|
||||
if (*(arg.data()) != '-') {
|
||||
mArgs.push_back(arg.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (Flag& flag : mFlags) {
|
||||
if (arg == flag.name) {
|
||||
if (flag.numArgs > 0) {
|
||||
i++;
|
||||
if (i >= args.size()) {
|
||||
*outError << flag.name << " missing argument.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
flag.action(args[i]);
|
||||
} else {
|
||||
flag.action({});
|
||||
}
|
||||
flag.parsed = true;
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
*outError << "unknown option '" << arg << "'.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
StringPiece arg = args[i];
|
||||
if (*(arg.data()) != '-') {
|
||||
mArgs.push_back(arg.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required && !flag.parsed) {
|
||||
*outError << "missing required flag " << flag.name << "\n\n";
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (Flag& flag : mFlags) {
|
||||
if (arg == flag.name) {
|
||||
if (flag.numArgs > 0) {
|
||||
i++;
|
||||
if (i >= args.size()) {
|
||||
*outError << flag.name << " missing argument.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
flag.action(args[i]);
|
||||
} else {
|
||||
flag.action({});
|
||||
}
|
||||
flag.parsed = true;
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
if (!match) {
|
||||
*outError << "unknown option '" << arg << "'.\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
if (flag.required && !flag.parsed) {
|
||||
*outError << "missing required flag " << flag.name << "\n\n";
|
||||
usage(command, outError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Flags::getArgs() {
|
||||
return mArgs;
|
||||
}
|
||||
const std::vector<std::string>& Flags::getArgs() { return mArgs; }
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -29,42 +29,45 @@
|
||||
namespace aapt {
|
||||
|
||||
class Flags {
|
||||
public:
|
||||
Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value);
|
||||
Flags& requiredFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Maybe<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name, const StringPiece& description,
|
||||
std::unordered_set<std::string>* value);
|
||||
Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
bool* value);
|
||||
public:
|
||||
Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value);
|
||||
Flags& requiredFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Maybe<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::unordered_set<std::string>* value);
|
||||
Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
bool* value);
|
||||
|
||||
void usage(const StringPiece& command, std::ostream* out);
|
||||
void usage(const StringPiece& command, std::ostream* out);
|
||||
|
||||
bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
|
||||
std::ostream* outError);
|
||||
bool parse(const StringPiece& command, const std::vector<StringPiece>& args,
|
||||
std::ostream* outError);
|
||||
|
||||
const std::vector<std::string>& getArgs();
|
||||
const std::vector<std::string>& getArgs();
|
||||
|
||||
private:
|
||||
struct Flag {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<bool(const StringPiece& value)> action;
|
||||
bool required;
|
||||
size_t numArgs;
|
||||
private:
|
||||
struct Flag {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<bool(const StringPiece& value)> action;
|
||||
bool required;
|
||||
size_t numArgs;
|
||||
|
||||
bool parsed;
|
||||
};
|
||||
bool parsed;
|
||||
};
|
||||
|
||||
std::vector<Flag> mFlags;
|
||||
std::vector<std::string> mArgs;
|
||||
std::vector<Flag> mFlags;
|
||||
std::vector<std::string> mArgs;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_FLAGS_H
|
||||
#endif // AAPT_FLAGS_H
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "Locale.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -27,225 +27,226 @@ namespace aapt {
|
||||
using android::ResTable_config;
|
||||
|
||||
void LocaleValue::setLanguage(const char* languageChars) {
|
||||
size_t i = 0;
|
||||
while ((*languageChars) != '\0') {
|
||||
language[i++] = ::tolower(*languageChars);
|
||||
languageChars++;
|
||||
}
|
||||
size_t i = 0;
|
||||
while ((*languageChars) != '\0') {
|
||||
language[i++] = ::tolower(*languageChars);
|
||||
languageChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setRegion(const char* regionChars) {
|
||||
size_t i = 0;
|
||||
while ((*regionChars) != '\0') {
|
||||
region[i++] = ::toupper(*regionChars);
|
||||
regionChars++;
|
||||
}
|
||||
size_t i = 0;
|
||||
while ((*regionChars) != '\0') {
|
||||
region[i++] = ::toupper(*regionChars);
|
||||
regionChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setScript(const char* scriptChars) {
|
||||
size_t i = 0;
|
||||
while ((*scriptChars) != '\0') {
|
||||
if (i == 0) {
|
||||
script[i++] = ::toupper(*scriptChars);
|
||||
} else {
|
||||
script[i++] = ::tolower(*scriptChars);
|
||||
}
|
||||
scriptChars++;
|
||||
size_t i = 0;
|
||||
while ((*scriptChars) != '\0') {
|
||||
if (i == 0) {
|
||||
script[i++] = ::toupper(*scriptChars);
|
||||
} else {
|
||||
script[i++] = ::tolower(*scriptChars);
|
||||
}
|
||||
scriptChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setVariant(const char* variantChars) {
|
||||
size_t i = 0;
|
||||
while ((*variantChars) != '\0') {
|
||||
variant[i++] = *variantChars;
|
||||
variantChars++;
|
||||
}
|
||||
size_t i = 0;
|
||||
while ((*variantChars) != '\0') {
|
||||
variant[i++] = *variantChars;
|
||||
variantChars++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool isAlpha(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
||||
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
||||
}
|
||||
|
||||
static inline bool isNumber(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
||||
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
||||
}
|
||||
|
||||
bool LocaleValue::initFromFilterString(const StringPiece& str) {
|
||||
// A locale (as specified in the filter) is an underscore separated name such
|
||||
// as "en_US", "en_Latn_US", or "en_US_POSIX".
|
||||
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
|
||||
// A locale (as specified in the filter) is an underscore separated name such
|
||||
// as "en_US", "en_Latn_US", or "en_US_POSIX".
|
||||
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
|
||||
|
||||
const int numTags = parts.size();
|
||||
bool valid = false;
|
||||
if (numTags >= 1) {
|
||||
const std::string& lang = parts[0];
|
||||
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
||||
setLanguage(lang.c_str());
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
const int numTags = parts.size();
|
||||
bool valid = false;
|
||||
if (numTags >= 1) {
|
||||
const std::string& lang = parts[0];
|
||||
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
||||
setLanguage(lang.c_str());
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid || numTags == 1) {
|
||||
return valid;
|
||||
}
|
||||
if (!valid || numTags == 1) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part2 = parts[1];
|
||||
if ((part2.length() == 2 && isAlpha(part2)) ||
|
||||
(part2.length() == 3 && isNumber(part2))) {
|
||||
setRegion(part2.c_str());
|
||||
} else if (part2.length() == 4 && isAlpha(part2)) {
|
||||
setScript(part2.c_str());
|
||||
} else if (part2.length() >= 4 && part2.length() <= 8) {
|
||||
setVariant(part2.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part2 = parts[1];
|
||||
if ((part2.length() == 2 && isAlpha(part2)) ||
|
||||
(part2.length() == 3 && isNumber(part2))) {
|
||||
setRegion(part2.c_str());
|
||||
} else if (part2.length() == 4 && isAlpha(part2)) {
|
||||
setScript(part2.c_str());
|
||||
} else if (part2.length() >= 4 && part2.length() <= 8) {
|
||||
setVariant(part2.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 2) {
|
||||
return valid;
|
||||
}
|
||||
if (!valid || numTags == 2) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part3 = parts[2];
|
||||
if (((part3.length() == 2 && isAlpha(part3)) ||
|
||||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
|
||||
setRegion(part3.c_str());
|
||||
} else if (part3.length() >= 4 && part3.length() <= 8) {
|
||||
setVariant(part3.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part3 = parts[2];
|
||||
if (((part3.length() == 2 && isAlpha(part3)) ||
|
||||
(part3.length() == 3 && isNumber(part3))) &&
|
||||
script[0]) {
|
||||
setRegion(part3.c_str());
|
||||
} else if (part3.length() >= 4 && part3.length() <= 8) {
|
||||
setVariant(part3.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 3) {
|
||||
return valid;
|
||||
}
|
||||
if (!valid || numTags == 3) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
const std::string& part4 = parts[3];
|
||||
if (part4.length() >= 4 && part4.length() <= 8) {
|
||||
setVariant(part4.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
const std::string& part4 = parts[3];
|
||||
if (part4.length() >= 4 && part4.length() <= 8) {
|
||||
setVariant(part4.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags > 4) {
|
||||
return false;
|
||||
}
|
||||
if (!valid || numTags > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end) {
|
||||
const std::vector<std::string>::iterator startIter = iter;
|
||||
std::vector<std::string>::iterator end) {
|
||||
const std::vector<std::string>::iterator startIter = iter;
|
||||
|
||||
std::string& part = *iter;
|
||||
if (part[0] == 'b' && part[1] == '+') {
|
||||
// This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
|
||||
// except that the separator is "+" and not "-".
|
||||
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
|
||||
subtags.erase(subtags.begin());
|
||||
if (subtags.size() == 1) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
} else if (subtags.size() == 2) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
std::string& part = *iter;
|
||||
if (part[0] == 'b' && part[1] == '+') {
|
||||
// This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
|
||||
// except that the separator is "+" and not "-".
|
||||
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
|
||||
subtags.erase(subtags.begin());
|
||||
if (subtags.size() == 1) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
} else if (subtags.size() == 2) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
|
||||
// The second tag can either be a region, a variant or a script.
|
||||
switch (subtags[1].size()) {
|
||||
case 2:
|
||||
case 3:
|
||||
setRegion(subtags[1].c_str());
|
||||
break;
|
||||
case 4:
|
||||
if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
|
||||
// This is a variant: fall through
|
||||
} else {
|
||||
setScript(subtags[1].c_str());
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
setVariant(subtags[1].c_str());
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} else if (subtags.size() == 3) {
|
||||
// The language is always the first subtag.
|
||||
setLanguage(subtags[0].c_str());
|
||||
|
||||
// The second subtag can either be a script or a region code.
|
||||
// If its size is 4, it's a script code, else it's a region code.
|
||||
if (subtags[1].size() == 4) {
|
||||
setScript(subtags[1].c_str());
|
||||
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
||||
setRegion(subtags[1].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The third tag can either be a region code (if the second tag was
|
||||
// a script), else a variant code.
|
||||
if (subtags[2].size() >= 4) {
|
||||
setVariant(subtags[2].c_str());
|
||||
} else {
|
||||
setRegion(subtags[2].c_str());
|
||||
}
|
||||
} else if (subtags.size() == 4) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
// The second tag can either be a region, a variant or a script.
|
||||
switch (subtags[1].size()) {
|
||||
case 2:
|
||||
case 3:
|
||||
setRegion(subtags[1].c_str());
|
||||
break;
|
||||
case 4:
|
||||
if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
|
||||
// This is a variant: fall through
|
||||
} else {
|
||||
setScript(subtags[1].c_str());
|
||||
setRegion(subtags[2].c_str());
|
||||
setVariant(subtags[3].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
setVariant(subtags[1].c_str());
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} else if (subtags.size() == 3) {
|
||||
// The language is always the first subtag.
|
||||
setLanguage(subtags[0].c_str());
|
||||
|
||||
++iter;
|
||||
// The second subtag can either be a script or a region code.
|
||||
// If its size is 4, it's a script code, else it's a region code.
|
||||
if (subtags[1].size() == 4) {
|
||||
setScript(subtags[1].c_str());
|
||||
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
||||
setRegion(subtags[1].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The third tag can either be a region code (if the second tag was
|
||||
// a script), else a variant code.
|
||||
if (subtags[2].size() >= 4) {
|
||||
setVariant(subtags[2].c_str());
|
||||
} else {
|
||||
setRegion(subtags[2].c_str());
|
||||
}
|
||||
} else if (subtags.size() == 4) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
setScript(subtags[1].c_str());
|
||||
setRegion(subtags[2].c_str());
|
||||
setVariant(subtags[3].c_str());
|
||||
} else {
|
||||
if ((part.length() == 2 || part.length() == 3)
|
||||
&& isAlpha(part) && part != "car") {
|
||||
setLanguage(part.c_str());
|
||||
++iter;
|
||||
|
||||
if (iter != end) {
|
||||
const std::string& regionPart = *iter;
|
||||
if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
|
||||
setRegion(regionPart.c_str() + 1);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(iter - startIter);
|
||||
++iter;
|
||||
|
||||
} else {
|
||||
if ((part.length() == 2 || part.length() == 3) && isAlpha(part) &&
|
||||
part != "car") {
|
||||
setLanguage(part.c_str());
|
||||
++iter;
|
||||
|
||||
if (iter != end) {
|
||||
const std::string& regionPart = *iter;
|
||||
if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
|
||||
setRegion(regionPart.c_str() + 1);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(iter - startIter);
|
||||
}
|
||||
|
||||
void LocaleValue::initFromResTable(const ResTable_config& config) {
|
||||
config.unpackLanguage(language);
|
||||
config.unpackRegion(region);
|
||||
if (config.localeScript[0] && !config.localeScriptWasComputed) {
|
||||
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
||||
}
|
||||
config.unpackLanguage(language);
|
||||
config.unpackRegion(region);
|
||||
if (config.localeScript[0] && !config.localeScriptWasComputed) {
|
||||
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
||||
}
|
||||
|
||||
if (config.localeVariant[0]) {
|
||||
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
||||
}
|
||||
if (config.localeVariant[0]) {
|
||||
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::writeTo(ResTable_config* out) const {
|
||||
out->packLanguage(language);
|
||||
out->packRegion(region);
|
||||
out->packLanguage(language);
|
||||
out->packRegion(region);
|
||||
|
||||
if (script[0]) {
|
||||
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
||||
}
|
||||
if (script[0]) {
|
||||
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
||||
}
|
||||
|
||||
if (variant[0]) {
|
||||
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
||||
}
|
||||
if (variant[0]) {
|
||||
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -29,86 +29,84 @@ namespace aapt {
|
||||
* A convenience class to build and parse locales.
|
||||
*/
|
||||
struct LocaleValue {
|
||||
char language[4];
|
||||
char region[4];
|
||||
char script[4];
|
||||
char variant[8];
|
||||
char language[4];
|
||||
char region[4];
|
||||
char script[4];
|
||||
char variant[8];
|
||||
|
||||
inline LocaleValue();
|
||||
inline LocaleValue();
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from a config string.
|
||||
*/
|
||||
bool initFromFilterString(const StringPiece& config);
|
||||
/**
|
||||
* Initialize this LocaleValue from a config string.
|
||||
*/
|
||||
bool initFromFilterString(const StringPiece& config);
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from parts of a vector.
|
||||
*/
|
||||
ssize_t initFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end);
|
||||
/**
|
||||
* Initialize this LocaleValue from parts of a vector.
|
||||
*/
|
||||
ssize_t initFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end);
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from a ResTable_config.
|
||||
*/
|
||||
void initFromResTable(const android::ResTable_config& config);
|
||||
/**
|
||||
* Initialize this LocaleValue from a ResTable_config.
|
||||
*/
|
||||
void initFromResTable(const android::ResTable_config& config);
|
||||
|
||||
/**
|
||||
* Set the locale in a ResTable_config from this LocaleValue.
|
||||
*/
|
||||
void writeTo(android::ResTable_config* out) const;
|
||||
/**
|
||||
* Set the locale in a ResTable_config from this LocaleValue.
|
||||
*/
|
||||
void writeTo(android::ResTable_config* out) const;
|
||||
|
||||
inline int compare(const LocaleValue& other) const;
|
||||
inline int compare(const LocaleValue& other) const;
|
||||
|
||||
inline bool operator<(const LocaleValue& o) const;
|
||||
inline bool operator<=(const LocaleValue& o) const;
|
||||
inline bool operator==(const LocaleValue& o) const;
|
||||
inline bool operator!=(const LocaleValue& o) const;
|
||||
inline bool operator>=(const LocaleValue& o) const;
|
||||
inline bool operator>(const LocaleValue& o) const;
|
||||
inline bool operator<(const LocaleValue& o) const;
|
||||
inline bool operator<=(const LocaleValue& o) const;
|
||||
inline bool operator==(const LocaleValue& o) const;
|
||||
inline bool operator!=(const LocaleValue& o) const;
|
||||
inline bool operator>=(const LocaleValue& o) const;
|
||||
inline bool operator>(const LocaleValue& o) const;
|
||||
|
||||
private:
|
||||
void setLanguage(const char* language);
|
||||
void setRegion(const char* language);
|
||||
void setScript(const char* script);
|
||||
void setVariant(const char* variant);
|
||||
private:
|
||||
void setLanguage(const char* language);
|
||||
void setRegion(const char* language);
|
||||
void setScript(const char* script);
|
||||
void setVariant(const char* variant);
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
LocaleValue::LocaleValue() {
|
||||
memset(this, 0, sizeof(LocaleValue));
|
||||
}
|
||||
LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); }
|
||||
|
||||
int LocaleValue::compare(const LocaleValue& other) const {
|
||||
return memcmp(this, &other, sizeof(LocaleValue));
|
||||
return memcmp(this, &other, sizeof(LocaleValue));
|
||||
}
|
||||
|
||||
bool LocaleValue::operator<(const LocaleValue& o) const {
|
||||
return compare(o) < 0;
|
||||
return compare(o) < 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator<=(const LocaleValue& o) const {
|
||||
return compare(o) <= 0;
|
||||
return compare(o) <= 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator==(const LocaleValue& o) const {
|
||||
return compare(o) == 0;
|
||||
return compare(o) == 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator!=(const LocaleValue& o) const {
|
||||
return compare(o) != 0;
|
||||
return compare(o) != 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator>=(const LocaleValue& o) const {
|
||||
return compare(o) >= 0;
|
||||
return compare(o) >= 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator>(const LocaleValue& o) const {
|
||||
return compare(o) > 0;
|
||||
return compare(o) > 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LOCALE_VALUE_H
|
||||
#endif // AAPT_LOCALE_VALUE_H
|
||||
|
||||
@@ -22,61 +22,73 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
|
||||
}
|
||||
static ::testing::AssertionResult TestLanguage(const char* input,
|
||||
const char* lang) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input
|
||||
<< "'.";
|
||||
}
|
||||
|
||||
if (count != 1) {
|
||||
return ::testing::AssertionFailure() << count
|
||||
<< " parts were consumed parsing '" << input << "' but expected 1.";
|
||||
}
|
||||
if (count != 1) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< count << " parts were consumed parsing '" << input
|
||||
<< "' but expected 1.";
|
||||
}
|
||||
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << lang << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
|
||||
0) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected " << lang << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
|
||||
return ::testing::AssertionSuccess();
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang,
|
||||
static ::testing::AssertionResult TestLanguageRegion(const char* input,
|
||||
const char* lang,
|
||||
const char* region) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
|
||||
}
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input
|
||||
<< "'.";
|
||||
}
|
||||
|
||||
if (count != 2) {
|
||||
return ::testing::AssertionFailure() << count
|
||||
<< " parts were consumed parsing '" << input << "' but expected 2.";
|
||||
}
|
||||
if (count != 2) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< count << " parts were consumed parsing '" << input
|
||||
<< "' but expected 2.";
|
||||
}
|
||||
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << input << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
|
||||
0) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected " << input << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
|
||||
if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << region << " but got "
|
||||
<< std::string(lv.region, sizeof(lv.region)) << ".";
|
||||
}
|
||||
if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) !=
|
||||
0) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected " << region << " but got "
|
||||
<< std::string(lv.region, sizeof(lv.region)) << ".";
|
||||
}
|
||||
|
||||
return ::testing::AssertionSuccess();
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseLanguage) {
|
||||
EXPECT_TRUE(TestLanguage("en", "en"));
|
||||
EXPECT_TRUE(TestLanguage("fr", "fr"));
|
||||
EXPECT_FALSE(TestLanguage("land", ""));
|
||||
EXPECT_TRUE(TestLanguage("fr-land", "fr"));
|
||||
EXPECT_TRUE(TestLanguage("en", "en"));
|
||||
EXPECT_TRUE(TestLanguage("fr", "fr"));
|
||||
EXPECT_FALSE(TestLanguage("land", ""));
|
||||
EXPECT_TRUE(TestLanguage("fr-land", "fr"));
|
||||
|
||||
EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
|
||||
EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -28,9 +28,9 @@ static const char* sMajorVersion = "2";
|
||||
static const char* sMinorVersion = "2";
|
||||
|
||||
int printVersion() {
|
||||
std::cerr << "Android Asset Packaging Tool (aapt) "
|
||||
<< sMajorVersion << "." << sMinorVersion << std::endl;
|
||||
return 0;
|
||||
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
|
||||
<< sMinorVersion << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern int compile(const std::vector<StringPiece>& args);
|
||||
@@ -38,35 +38,36 @@ extern int link(const std::vector<StringPiece>& args);
|
||||
extern int dump(const std::vector<StringPiece>& args);
|
||||
extern int diff(const std::vector<StringPiece>& args);
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc >= 2) {
|
||||
argv += 1;
|
||||
argc -= 1;
|
||||
if (argc >= 2) {
|
||||
argv += 1;
|
||||
argc -= 1;
|
||||
|
||||
std::vector<aapt::StringPiece> args;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
|
||||
aapt::StringPiece command(argv[0]);
|
||||
if (command == "compile" || command == "c") {
|
||||
return aapt::compile(args);
|
||||
} else if (command == "link" || command == "l") {
|
||||
return aapt::link(args);
|
||||
} else if (command == "dump" || command == "d") {
|
||||
return aapt::dump(args);
|
||||
} else if (command == "diff") {
|
||||
return aapt::diff(args);
|
||||
} else if (command == "version") {
|
||||
return aapt::printVersion();
|
||||
}
|
||||
std::cerr << "unknown command '" << command << "'\n";
|
||||
} else {
|
||||
std::cerr << "no command specified\n";
|
||||
std::vector<aapt::StringPiece> args;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
|
||||
std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." << std::endl;
|
||||
return 1;
|
||||
aapt::StringPiece command(argv[0]);
|
||||
if (command == "compile" || command == "c") {
|
||||
return aapt::compile(args);
|
||||
} else if (command == "link" || command == "l") {
|
||||
return aapt::link(args);
|
||||
} else if (command == "dump" || command == "d") {
|
||||
return aapt::dump(args);
|
||||
} else if (command == "diff") {
|
||||
return aapt::diff(args);
|
||||
} else if (command == "version") {
|
||||
return aapt::printVersion();
|
||||
}
|
||||
std::cerr << "unknown command '" << command << "'\n";
|
||||
} else {
|
||||
std::cerr << "no command specified\n";
|
||||
}
|
||||
|
||||
std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..."
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -26,69 +26,71 @@
|
||||
namespace aapt {
|
||||
|
||||
struct NameManglerPolicy {
|
||||
/**
|
||||
* Represents the package we are trying to build. References pointing
|
||||
* to this package are not mangled, and mangled references inherit this package name.
|
||||
*/
|
||||
std::string targetPackageName;
|
||||
/**
|
||||
* Represents the package we are trying to build. References pointing
|
||||
* to this package are not mangled, and mangled references inherit this
|
||||
* package name.
|
||||
*/
|
||||
std::string targetPackageName;
|
||||
|
||||
/**
|
||||
* We must know which references to mangle, and which to keep (android vs. com.android.support).
|
||||
*/
|
||||
std::set<std::string> packagesToMangle;
|
||||
/**
|
||||
* We must know which references to mangle, and which to keep (android vs.
|
||||
* com.android.support).
|
||||
*/
|
||||
std::set<std::string> packagesToMangle;
|
||||
};
|
||||
|
||||
class NameMangler {
|
||||
private:
|
||||
NameManglerPolicy mPolicy;
|
||||
private:
|
||||
NameManglerPolicy mPolicy;
|
||||
|
||||
public:
|
||||
explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {
|
||||
public:
|
||||
explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {}
|
||||
|
||||
Maybe<ResourceName> mangleName(const ResourceName& name) {
|
||||
if (mPolicy.targetPackageName == name.package ||
|
||||
mPolicy.packagesToMangle.count(name.package) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<ResourceName> mangleName(const ResourceName& name) {
|
||||
if (mPolicy.targetPackageName == name.package ||
|
||||
mPolicy.packagesToMangle.count(name.package) == 0) {
|
||||
return {};
|
||||
}
|
||||
std::string mangledEntryName = mangleEntry(name.package, name.entry);
|
||||
return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName);
|
||||
}
|
||||
|
||||
std::string mangledEntryName = mangleEntry(name.package, name.entry);
|
||||
return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName);
|
||||
bool shouldMangle(const std::string& package) const {
|
||||
if (package.empty() || mPolicy.targetPackageName == package) {
|
||||
return false;
|
||||
}
|
||||
return mPolicy.packagesToMangle.count(package) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mangled name that is a combination of `name` and `package`.
|
||||
* The mangled name should contain symbols that are illegal to define in XML,
|
||||
* so that there will never be name mangling collisions.
|
||||
*/
|
||||
static std::string mangleEntry(const std::string& package,
|
||||
const std::string& name) {
|
||||
return package + "$" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmangles the name in `outName`, storing the correct name back in `outName`
|
||||
* and the package in `outPackage`. Returns true if the name was unmangled or
|
||||
* false if the name was never mangled to begin with.
|
||||
*/
|
||||
static bool unmangle(std::string* outName, std::string* outPackage) {
|
||||
size_t pivot = outName->find('$');
|
||||
if (pivot == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldMangle(const std::string& package) const {
|
||||
if (package.empty() || mPolicy.targetPackageName == package) {
|
||||
return false;
|
||||
}
|
||||
return mPolicy.packagesToMangle.count(package) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mangled name that is a combination of `name` and `package`.
|
||||
* The mangled name should contain symbols that are illegal to define in XML,
|
||||
* so that there will never be name mangling collisions.
|
||||
*/
|
||||
static std::string mangleEntry(const std::string& package, const std::string& name) {
|
||||
return package + "$" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmangles the name in `outName`, storing the correct name back in `outName`
|
||||
* and the package in `outPackage`. Returns true if the name was unmangled or
|
||||
* false if the name was never mangled to begin with.
|
||||
*/
|
||||
static bool unmangle(std::string* outName, std::string* outPackage) {
|
||||
size_t pivot = outName->find('$');
|
||||
if (pivot == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outPackage->assign(outName->data(), pivot);
|
||||
outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
|
||||
return true;
|
||||
}
|
||||
outPackage->assign(outName->data(), pivot);
|
||||
outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_NAME_MANGLER_H
|
||||
#endif // AAPT_NAME_MANGLER_H
|
||||
|
||||
@@ -22,25 +22,25 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(NameManglerTest, MangleName) {
|
||||
std::string package = "android.appcompat";
|
||||
std::string name = "Platform.AppCompat";
|
||||
std::string package = "android.appcompat";
|
||||
std::string name = "Platform.AppCompat";
|
||||
|
||||
std::string mangledName = NameMangler::mangleEntry(package, name);
|
||||
EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat");
|
||||
std::string mangledName = NameMangler::mangleEntry(package, name);
|
||||
EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat");
|
||||
|
||||
std::string unmangledPackage;
|
||||
std::string unmangledName = mangledName;
|
||||
ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage));
|
||||
EXPECT_EQ(unmangledName, "Platform.AppCompat");
|
||||
EXPECT_EQ(unmangledPackage, "android.appcompat");
|
||||
std::string unmangledPackage;
|
||||
std::string unmangledName = mangledName;
|
||||
ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage));
|
||||
EXPECT_EQ(unmangledName, "Platform.AppCompat");
|
||||
EXPECT_EQ(unmangledPackage, "android.appcompat");
|
||||
}
|
||||
|
||||
TEST(NameManglerTest, IgnoreUnmangledName) {
|
||||
std::string package;
|
||||
std::string name = "foo_bar";
|
||||
std::string package;
|
||||
std::string name = "foo_bar";
|
||||
|
||||
EXPECT_FALSE(NameMangler::unmangle(&name, &package));
|
||||
EXPECT_EQ(name, "foo_bar");
|
||||
EXPECT_FALSE(NameMangler::unmangle(&name, &package));
|
||||
EXPECT_EQ(name, "foo_bar");
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -23,74 +23,97 @@
|
||||
namespace aapt {
|
||||
|
||||
StringPiece toString(ResourceType type) {
|
||||
switch (type) {
|
||||
case ResourceType::kAnim: return "anim";
|
||||
case ResourceType::kAnimator: return "animator";
|
||||
case ResourceType::kArray: return "array";
|
||||
case ResourceType::kAttr: return "attr";
|
||||
case ResourceType::kAttrPrivate: return "^attr-private";
|
||||
case ResourceType::kBool: return "bool";
|
||||
case ResourceType::kColor: return "color";
|
||||
case ResourceType::kDimen: return "dimen";
|
||||
case ResourceType::kDrawable: return "drawable";
|
||||
case ResourceType::kFraction: return "fraction";
|
||||
case ResourceType::kId: return "id";
|
||||
case ResourceType::kInteger: return "integer";
|
||||
case ResourceType::kInterpolator: return "interpolator";
|
||||
case ResourceType::kLayout: return "layout";
|
||||
case ResourceType::kMenu: return "menu";
|
||||
case ResourceType::kMipmap: return "mipmap";
|
||||
case ResourceType::kPlurals: return "plurals";
|
||||
case ResourceType::kRaw: return "raw";
|
||||
case ResourceType::kString: return "string";
|
||||
case ResourceType::kStyle: return "style";
|
||||
case ResourceType::kStyleable: return "styleable";
|
||||
case ResourceType::kTransition: return "transition";
|
||||
case ResourceType::kXml: return "xml";
|
||||
}
|
||||
return {};
|
||||
switch (type) {
|
||||
case ResourceType::kAnim:
|
||||
return "anim";
|
||||
case ResourceType::kAnimator:
|
||||
return "animator";
|
||||
case ResourceType::kArray:
|
||||
return "array";
|
||||
case ResourceType::kAttr:
|
||||
return "attr";
|
||||
case ResourceType::kAttrPrivate:
|
||||
return "^attr-private";
|
||||
case ResourceType::kBool:
|
||||
return "bool";
|
||||
case ResourceType::kColor:
|
||||
return "color";
|
||||
case ResourceType::kDimen:
|
||||
return "dimen";
|
||||
case ResourceType::kDrawable:
|
||||
return "drawable";
|
||||
case ResourceType::kFraction:
|
||||
return "fraction";
|
||||
case ResourceType::kId:
|
||||
return "id";
|
||||
case ResourceType::kInteger:
|
||||
return "integer";
|
||||
case ResourceType::kInterpolator:
|
||||
return "interpolator";
|
||||
case ResourceType::kLayout:
|
||||
return "layout";
|
||||
case ResourceType::kMenu:
|
||||
return "menu";
|
||||
case ResourceType::kMipmap:
|
||||
return "mipmap";
|
||||
case ResourceType::kPlurals:
|
||||
return "plurals";
|
||||
case ResourceType::kRaw:
|
||||
return "raw";
|
||||
case ResourceType::kString:
|
||||
return "string";
|
||||
case ResourceType::kStyle:
|
||||
return "style";
|
||||
case ResourceType::kStyleable:
|
||||
return "styleable";
|
||||
case ResourceType::kTransition:
|
||||
return "transition";
|
||||
case ResourceType::kXml:
|
||||
return "xml";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static const std::map<StringPiece, ResourceType> sResourceTypeMap {
|
||||
{ "anim", ResourceType::kAnim },
|
||||
{ "animator", ResourceType::kAnimator },
|
||||
{ "array", ResourceType::kArray },
|
||||
{ "attr", ResourceType::kAttr },
|
||||
{ "^attr-private", ResourceType::kAttrPrivate },
|
||||
{ "bool", ResourceType::kBool },
|
||||
{ "color", ResourceType::kColor },
|
||||
{ "dimen", ResourceType::kDimen },
|
||||
{ "drawable", ResourceType::kDrawable },
|
||||
{ "fraction", ResourceType::kFraction },
|
||||
{ "id", ResourceType::kId },
|
||||
{ "integer", ResourceType::kInteger },
|
||||
{ "interpolator", ResourceType::kInterpolator },
|
||||
{ "layout", ResourceType::kLayout },
|
||||
{ "menu", ResourceType::kMenu },
|
||||
{ "mipmap", ResourceType::kMipmap },
|
||||
{ "plurals", ResourceType::kPlurals },
|
||||
{ "raw", ResourceType::kRaw },
|
||||
{ "string", ResourceType::kString },
|
||||
{ "style", ResourceType::kStyle },
|
||||
{ "styleable", ResourceType::kStyleable },
|
||||
{ "transition", ResourceType::kTransition },
|
||||
{ "xml", ResourceType::kXml },
|
||||
static const std::map<StringPiece, ResourceType> sResourceTypeMap{
|
||||
{"anim", ResourceType::kAnim},
|
||||
{"animator", ResourceType::kAnimator},
|
||||
{"array", ResourceType::kArray},
|
||||
{"attr", ResourceType::kAttr},
|
||||
{"^attr-private", ResourceType::kAttrPrivate},
|
||||
{"bool", ResourceType::kBool},
|
||||
{"color", ResourceType::kColor},
|
||||
{"dimen", ResourceType::kDimen},
|
||||
{"drawable", ResourceType::kDrawable},
|
||||
{"fraction", ResourceType::kFraction},
|
||||
{"id", ResourceType::kId},
|
||||
{"integer", ResourceType::kInteger},
|
||||
{"interpolator", ResourceType::kInterpolator},
|
||||
{"layout", ResourceType::kLayout},
|
||||
{"menu", ResourceType::kMenu},
|
||||
{"mipmap", ResourceType::kMipmap},
|
||||
{"plurals", ResourceType::kPlurals},
|
||||
{"raw", ResourceType::kRaw},
|
||||
{"string", ResourceType::kString},
|
||||
{"style", ResourceType::kStyle},
|
||||
{"styleable", ResourceType::kStyleable},
|
||||
{"transition", ResourceType::kTransition},
|
||||
{"xml", ResourceType::kXml},
|
||||
};
|
||||
|
||||
const ResourceType* parseResourceType(const StringPiece& str) {
|
||||
auto iter = sResourceTypeMap.find(str);
|
||||
if (iter == std::end(sResourceTypeMap)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &iter->second;
|
||||
auto iter = sResourceTypeMap.find(str);
|
||||
if (iter == std::end(sResourceTypeMap)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
bool operator<(const ResourceKey& a, const ResourceKey& b) {
|
||||
return std::tie(a.name, a.config) < std::tie(b.name, b.config);
|
||||
return std::tie(a.name, a.config) < std::tie(b.name, b.config);
|
||||
}
|
||||
|
||||
bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) {
|
||||
return std::tie(a.name, a.config) < std::tie(b.name, b.config);
|
||||
return std::tie(a.name, a.config) < std::tie(b.name, b.config);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -37,29 +37,29 @@ namespace aapt {
|
||||
* to the 'type' in package:type/entry.
|
||||
*/
|
||||
enum class ResourceType {
|
||||
kAnim,
|
||||
kAnimator,
|
||||
kArray,
|
||||
kAttr,
|
||||
kAttrPrivate,
|
||||
kBool,
|
||||
kColor,
|
||||
kDimen,
|
||||
kDrawable,
|
||||
kFraction,
|
||||
kId,
|
||||
kInteger,
|
||||
kInterpolator,
|
||||
kLayout,
|
||||
kMenu,
|
||||
kMipmap,
|
||||
kPlurals,
|
||||
kRaw,
|
||||
kString,
|
||||
kStyle,
|
||||
kStyleable,
|
||||
kTransition,
|
||||
kXml,
|
||||
kAnim,
|
||||
kAnimator,
|
||||
kArray,
|
||||
kAttr,
|
||||
kAttrPrivate,
|
||||
kBool,
|
||||
kColor,
|
||||
kDimen,
|
||||
kDrawable,
|
||||
kFraction,
|
||||
kId,
|
||||
kInteger,
|
||||
kInterpolator,
|
||||
kLayout,
|
||||
kMenu,
|
||||
kMipmap,
|
||||
kPlurals,
|
||||
kRaw,
|
||||
kString,
|
||||
kStyle,
|
||||
kStyleable,
|
||||
kTransition,
|
||||
kXml,
|
||||
};
|
||||
|
||||
StringPiece toString(ResourceType type);
|
||||
@@ -75,17 +75,17 @@ const ResourceType* parseResourceType(const StringPiece& str);
|
||||
* a resource in the ResourceTable.
|
||||
*/
|
||||
struct ResourceName {
|
||||
std::string package;
|
||||
ResourceType type;
|
||||
std::string entry;
|
||||
std::string package;
|
||||
ResourceType type;
|
||||
std::string entry;
|
||||
|
||||
ResourceName() : type(ResourceType::kRaw) {}
|
||||
ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
|
||||
ResourceName() : type(ResourceType::kRaw) {}
|
||||
ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
|
||||
|
||||
int compare(const ResourceName& other) const;
|
||||
int compare(const ResourceName& other) const;
|
||||
|
||||
bool isValid() const;
|
||||
std::string toString() const;
|
||||
bool isValid() const;
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -95,21 +95,21 @@ struct ResourceName {
|
||||
* of the original string.
|
||||
*/
|
||||
struct ResourceNameRef {
|
||||
StringPiece package;
|
||||
ResourceType type;
|
||||
StringPiece entry;
|
||||
StringPiece package;
|
||||
ResourceType type;
|
||||
StringPiece entry;
|
||||
|
||||
ResourceNameRef() = default;
|
||||
ResourceNameRef(const ResourceNameRef&) = default;
|
||||
ResourceNameRef(ResourceNameRef&&) = default;
|
||||
ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
|
||||
ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
|
||||
ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
|
||||
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
|
||||
ResourceNameRef& operator=(const ResourceName& rhs);
|
||||
ResourceNameRef() = default;
|
||||
ResourceNameRef(const ResourceNameRef&) = default;
|
||||
ResourceNameRef(ResourceNameRef&&) = default;
|
||||
ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
|
||||
ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
|
||||
ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
|
||||
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
|
||||
ResourceNameRef& operator=(const ResourceName& rhs);
|
||||
|
||||
ResourceName toResourceName() const;
|
||||
bool isValid() const;
|
||||
ResourceName toResourceName() const;
|
||||
bool isValid() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,64 +124,66 @@ struct ResourceNameRef {
|
||||
* EEEE: 16 bit entry identifier.
|
||||
*/
|
||||
struct ResourceId {
|
||||
uint32_t id;
|
||||
uint32_t id;
|
||||
|
||||
ResourceId();
|
||||
ResourceId(const ResourceId& rhs);
|
||||
ResourceId(uint32_t resId); // NOLINT(implicit)
|
||||
ResourceId(uint8_t p, uint8_t t, uint16_t e);
|
||||
ResourceId();
|
||||
ResourceId(const ResourceId& rhs);
|
||||
ResourceId(uint32_t resId); // NOLINT(implicit)
|
||||
ResourceId(uint8_t p, uint8_t t, uint16_t e);
|
||||
|
||||
bool isValid() const;
|
||||
uint8_t packageId() const;
|
||||
uint8_t typeId() const;
|
||||
uint16_t entryId() const;
|
||||
bool isValid() const;
|
||||
uint8_t packageId() const;
|
||||
uint8_t typeId() const;
|
||||
uint16_t entryId() const;
|
||||
};
|
||||
|
||||
struct SourcedResourceName {
|
||||
ResourceName name;
|
||||
size_t line;
|
||||
ResourceName name;
|
||||
size_t line;
|
||||
};
|
||||
|
||||
struct ResourceFile {
|
||||
// Name
|
||||
ResourceName name;
|
||||
// Name
|
||||
ResourceName name;
|
||||
|
||||
// Configuration
|
||||
ConfigDescription config;
|
||||
// Configuration
|
||||
ConfigDescription config;
|
||||
|
||||
// Source
|
||||
Source source;
|
||||
// Source
|
||||
Source source;
|
||||
|
||||
// Exported symbols
|
||||
std::vector<SourcedResourceName> exportedSymbols;
|
||||
// Exported symbols
|
||||
std::vector<SourcedResourceName> exportedSymbols;
|
||||
};
|
||||
|
||||
/**
|
||||
* Useful struct used as a key to represent a unique resource in associative containers.
|
||||
* Useful struct used as a key to represent a unique resource in associative
|
||||
* containers.
|
||||
*/
|
||||
struct ResourceKey {
|
||||
ResourceName name;
|
||||
ConfigDescription config;
|
||||
ResourceName name;
|
||||
ConfigDescription config;
|
||||
};
|
||||
|
||||
bool operator<(const ResourceKey& a, const ResourceKey& b);
|
||||
|
||||
/**
|
||||
* Useful struct used as a key to represent a unique resource in associative containers.
|
||||
* Useful struct used as a key to represent a unique resource in associative
|
||||
* containers.
|
||||
* Holds a reference to the name, so that name better live longer than this key!
|
||||
*/
|
||||
struct ResourceKeyRef {
|
||||
ResourceNameRef name;
|
||||
ConfigDescription config;
|
||||
ResourceNameRef name;
|
||||
ConfigDescription config;
|
||||
|
||||
ResourceKeyRef() = default;
|
||||
ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) {
|
||||
}
|
||||
ResourceKeyRef() = default;
|
||||
ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c)
|
||||
: name(n), config(c) {}
|
||||
|
||||
/**
|
||||
* Prevent taking a reference to a temporary. This is bad.
|
||||
*/
|
||||
ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete;
|
||||
/**
|
||||
* Prevent taking a reference to a temporary. This is bad.
|
||||
*/
|
||||
ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete;
|
||||
};
|
||||
|
||||
bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
|
||||
@@ -190,193 +192,194 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
|
||||
// ResourceId implementation.
|
||||
//
|
||||
|
||||
inline ResourceId::ResourceId() : id(0) {
|
||||
}
|
||||
inline ResourceId::ResourceId() : id(0) {}
|
||||
|
||||
inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
|
||||
}
|
||||
inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {}
|
||||
|
||||
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
|
||||
}
|
||||
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {}
|
||||
|
||||
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {
|
||||
}
|
||||
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
|
||||
: id((p << 24) | (t << 16) | e) {}
|
||||
|
||||
inline bool ResourceId::isValid() const {
|
||||
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
|
||||
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::packageId() const {
|
||||
return static_cast<uint8_t>(id >> 24);
|
||||
return static_cast<uint8_t>(id >> 24);
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::typeId() const {
|
||||
return static_cast<uint8_t>(id >> 16);
|
||||
return static_cast<uint8_t>(id >> 16);
|
||||
}
|
||||
|
||||
inline uint16_t ResourceId::entryId() const {
|
||||
return static_cast<uint16_t>(id);
|
||||
return static_cast<uint16_t>(id);
|
||||
}
|
||||
|
||||
inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) {
|
||||
return lhs.id < rhs.id;
|
||||
return lhs.id < rhs.id;
|
||||
}
|
||||
|
||||
inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) {
|
||||
return lhs.id > rhs.id;
|
||||
return lhs.id > rhs.id;
|
||||
}
|
||||
|
||||
inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) {
|
||||
return lhs.id == rhs.id;
|
||||
return lhs.id == rhs.id;
|
||||
}
|
||||
|
||||
inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
|
||||
return lhs.id != rhs.id;
|
||||
return lhs.id != rhs.id;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) {
|
||||
std::ios_base::fmtflags oldFlags = out.flags();
|
||||
char oldFill = out.fill();
|
||||
out << "0x" << std::internal << std::setfill('0') << std::setw(8)
|
||||
<< std::hex << resId.id;
|
||||
out.flags(oldFlags);
|
||||
out.fill(oldFill);
|
||||
return out;
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceId& resId) {
|
||||
std::ios_base::fmtflags oldFlags = out.flags();
|
||||
char oldFill = out.fill();
|
||||
out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
|
||||
<< resId.id;
|
||||
out.flags(oldFlags);
|
||||
out.fill(oldFill);
|
||||
return out;
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceType implementation.
|
||||
//
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
|
||||
return out << toString(val);
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceType& val) {
|
||||
return out << toString(val);
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceName implementation.
|
||||
//
|
||||
|
||||
inline ResourceName::ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e) :
|
||||
package(p.toString()), type(t), entry(e.toString()) {
|
||||
}
|
||||
inline ResourceName::ResourceName(const StringPiece& p, ResourceType t,
|
||||
const StringPiece& e)
|
||||
: package(p.toString()), type(t), entry(e.toString()) {}
|
||||
|
||||
inline int ResourceName::compare(const ResourceName& other) const {
|
||||
int cmp = package.compare(other.package);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = static_cast<int>(type) - static_cast<int>(other.type);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = entry.compare(other.entry);
|
||||
return cmp;
|
||||
int cmp = package.compare(other.package);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = static_cast<int>(type) - static_cast<int>(other.type);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = entry.compare(other.entry);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
inline bool ResourceName::isValid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
inline bool operator<(const ResourceName& lhs, const ResourceName& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
< std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) <
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator==(const ResourceName& lhs, const ResourceName& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
== std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) ==
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
!= std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) !=
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
}
|
||||
return out << name.type << "/" << name.entry;
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceName& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
}
|
||||
return out << name.type << "/" << name.entry;
|
||||
}
|
||||
|
||||
inline std::string ResourceName::toString() const {
|
||||
std::stringstream stream;
|
||||
stream << *this;
|
||||
return stream.str();
|
||||
std::stringstream stream;
|
||||
stream << *this;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceNameRef implementation.
|
||||
//
|
||||
|
||||
inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) :
|
||||
package(rhs.package), type(rhs.type), entry(rhs.entry) {
|
||||
}
|
||||
inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
|
||||
: package(rhs.package), type(rhs.type), entry(rhs.entry) {}
|
||||
|
||||
inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t,
|
||||
const StringPiece& e) :
|
||||
package(p), type(t), entry(e) {
|
||||
}
|
||||
const StringPiece& e)
|
||||
: package(p), type(t), entry(e) {}
|
||||
|
||||
inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
|
||||
package = rhs.package;
|
||||
type = rhs.type;
|
||||
entry = rhs.entry;
|
||||
return *this;
|
||||
package = rhs.package;
|
||||
type = rhs.type;
|
||||
entry = rhs.entry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ResourceName ResourceNameRef::toResourceName() const {
|
||||
return ResourceName(package, type, entry);
|
||||
return ResourceName(package, type, entry);
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::isValid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
inline bool operator<(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
< std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) <
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator==(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
== std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) ==
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry)
|
||||
!= std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
return std::tie(lhs.package, lhs.type, lhs.entry) !=
|
||||
std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
}
|
||||
return out << name.type << "/" << name.entry;
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceNameRef& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
}
|
||||
return out << name.type << "/" << name.entry;
|
||||
}
|
||||
|
||||
inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
|
||||
return ResourceNameRef(lhs) < b;
|
||||
return ResourceNameRef(lhs) < b;
|
||||
}
|
||||
|
||||
inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
|
||||
return ResourceNameRef(lhs) != rhs;
|
||||
return ResourceNameRef(lhs) != rhs;
|
||||
}
|
||||
|
||||
inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
|
||||
return lhs.name == rhs.name && lhs.line == rhs.line;
|
||||
inline bool operator==(const SourcedResourceName& lhs,
|
||||
const SourcedResourceName& rhs) {
|
||||
return lhs.name == rhs.name && lhs.line == rhs.line;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
namespace std {
|
||||
|
||||
template <> struct hash<aapt::ResourceName> {
|
||||
size_t operator()(const aapt::ResourceName& name) const {
|
||||
android::hash_t h = 0;
|
||||
h = android::JenkinsHashMix(h, hash<string>()(name.package));
|
||||
h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
|
||||
h = android::JenkinsHashMix(h, hash<string>()(name.entry));
|
||||
return static_cast<size_t>(h);
|
||||
}
|
||||
template <>
|
||||
struct hash<aapt::ResourceName> {
|
||||
size_t operator()(const aapt::ResourceName& name) const {
|
||||
android::hash_t h = 0;
|
||||
h = android::JenkinsHashMix(h, hash<string>()(name.package));
|
||||
h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
|
||||
h = android::JenkinsHashMix(h, hash<string>()(name.entry));
|
||||
return static_cast<size_t>(h);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
} // namespace std
|
||||
|
||||
#endif // AAPT_RESOURCE_H
|
||||
#endif // AAPT_RESOURCE_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,79 +33,92 @@ namespace aapt {
|
||||
struct ParsedResource;
|
||||
|
||||
struct ResourceParserOptions {
|
||||
/**
|
||||
* Whether the default setting for this parser is to allow translation.
|
||||
*/
|
||||
bool translatable = true;
|
||||
/**
|
||||
* Whether the default setting for this parser is to allow translation.
|
||||
*/
|
||||
bool translatable = true;
|
||||
|
||||
/**
|
||||
* Whether positional arguments in formatted strings are treated as errors or warnings.
|
||||
*/
|
||||
bool errorOnPositionalArguments = true;
|
||||
/**
|
||||
* Whether positional arguments in formatted strings are treated as errors or
|
||||
* warnings.
|
||||
*/
|
||||
bool errorOnPositionalArguments = true;
|
||||
};
|
||||
|
||||
/*
|
||||
* Parses an XML file for resources and adds them to a ResourceTable.
|
||||
*/
|
||||
class ResourceParser {
|
||||
public:
|
||||
ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
|
||||
const ConfigDescription& config, const ResourceParserOptions& options = {});
|
||||
public:
|
||||
ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
|
||||
const ConfigDescription& config,
|
||||
const ResourceParserOptions& options = {});
|
||||
|
||||
ResourceParser(const ResourceParser&) = delete; // No copy.
|
||||
ResourceParser(const ResourceParser&) = delete; // No copy.
|
||||
|
||||
bool parse(xml::XmlPullParser* parser);
|
||||
bool parse(xml::XmlPullParser* parser);
|
||||
|
||||
private:
|
||||
/*
|
||||
* Parses the XML subtree as a StyleString (flattened XML representation for strings
|
||||
* with formatting). If successful, `outStyleString`
|
||||
* contains the escaped and whitespace trimmed text, while `outRawString`
|
||||
* contains the unescaped text. Returns true on success.
|
||||
*/
|
||||
bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
|
||||
StyleString* outStyleString);
|
||||
private:
|
||||
/*
|
||||
* Parses the XML subtree as a StyleString (flattened XML representation for
|
||||
* strings
|
||||
* with formatting). If successful, `outStyleString`
|
||||
* contains the escaped and whitespace trimmed text, while `outRawString`
|
||||
* contains the unescaped text. Returns true on success.
|
||||
*/
|
||||
bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
|
||||
StyleString* outStyleString);
|
||||
|
||||
/*
|
||||
* Parses the XML subtree and returns an Item.
|
||||
* The type of Item that can be parsed is denoted by the `typeMask`.
|
||||
* If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
|
||||
* RawString is returned. Otherwise this returns false;
|
||||
*/
|
||||
std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
|
||||
const bool allowRawValue);
|
||||
/*
|
||||
* Parses the XML subtree and returns an Item.
|
||||
* The type of Item that can be parsed is denoted by the `typeMask`.
|
||||
* If `allowRawValue` is true and the subtree can not be parsed as a regular
|
||||
* Item, then a
|
||||
* RawString is returned. Otherwise this returns false;
|
||||
*/
|
||||
std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser,
|
||||
const uint32_t typeMask,
|
||||
const bool allowRawValue);
|
||||
|
||||
bool parseResources(xml::XmlPullParser* parser);
|
||||
bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseResources(xml::XmlPullParser* parser);
|
||||
bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
|
||||
bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format);
|
||||
bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
|
||||
uint32_t format);
|
||||
bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
|
||||
bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
|
||||
Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
|
||||
const StringPiece& tag);
|
||||
bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
|
||||
bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
|
||||
bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parsePublicGroup(xml::XmlPullParser* parser,
|
||||
ParsedResource* outResource);
|
||||
bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseAddResource(xml::XmlPullParser* parser,
|
||||
ParsedResource* outResource);
|
||||
bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
|
||||
bool weak);
|
||||
Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
|
||||
const StringPiece& tag);
|
||||
bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
|
||||
bool parseDeclareStyleable(xml::XmlPullParser* parser,
|
||||
ParsedResource* outResource);
|
||||
bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool parseIntegerArray(xml::XmlPullParser* parser,
|
||||
ParsedResource* outResource);
|
||||
bool parseStringArray(xml::XmlPullParser* parser,
|
||||
ParsedResource* outResource);
|
||||
bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
|
||||
uint32_t typeMask);
|
||||
bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTable* mTable;
|
||||
Source mSource;
|
||||
ConfigDescription mConfig;
|
||||
ResourceParserOptions mOptions;
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTable* mTable;
|
||||
Source mSource;
|
||||
ConfigDescription mConfig;
|
||||
ResourceParserOptions mOptions;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_PARSER_H
|
||||
#endif // AAPT_RESOURCE_PARSER_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,266 +14,282 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "NameMangler.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
|
||||
return lhs->type < rhs;
|
||||
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs,
|
||||
ResourceType rhs) {
|
||||
return lhs->type < rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool lessThanStructWithName(const std::unique_ptr<T>& lhs,
|
||||
const StringPiece& rhs) {
|
||||
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
|
||||
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findPackage(const StringPiece& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
|
||||
for (auto& package : packages) {
|
||||
if (package->id && package->id.value() == id) {
|
||||
return package.get();
|
||||
}
|
||||
for (auto& package : packages) {
|
||||
if (package->id && package->id.value() == id) {
|
||||
return package.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name, Maybe<uint8_t> id) {
|
||||
ResourceTablePackage* package = findOrCreatePackage(name);
|
||||
if (id && !package->id) {
|
||||
package->id = id;
|
||||
return package;
|
||||
}
|
||||
|
||||
if (id && package->id && package->id.value() != id.value()) {
|
||||
return nullptr;
|
||||
}
|
||||
ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name,
|
||||
Maybe<uint8_t> id) {
|
||||
ResourceTablePackage* package = findOrCreatePackage(name);
|
||||
if (id && !package->id) {
|
||||
package->id = id;
|
||||
return package;
|
||||
}
|
||||
|
||||
if (id && package->id && package->id.value() != id.value()) {
|
||||
return nullptr;
|
||||
}
|
||||
return package;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
ResourceTablePackage* ResourceTable::findOrCreatePackage(
|
||||
const StringPiece& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>();
|
||||
newPackage->name = name.toString();
|
||||
return packages.emplace(iter, std::move(newPackage))->get();
|
||||
std::unique_ptr<ResourceTablePackage> newPackage =
|
||||
util::make_unique<ResourceTablePackage>();
|
||||
newPackage->name = name.toString();
|
||||
return packages.emplace(iter, std::move(newPackage))->get();
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return types.emplace(iter, new ResourceTableType(type))->get();
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return types.emplace(iter, new ResourceTableType(type))->get();
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findEntry(const StringPiece& name) {
|
||||
const auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
const auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece& name) {
|
||||
auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return entries.emplace(iter, new ResourceEntry(name))->get();
|
||||
auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return entries.emplace(iter, new ResourceEntry(name))->get();
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
|
||||
return findValue(config, StringPiece());
|
||||
return findValue(config, StringPiece());
|
||||
}
|
||||
|
||||
struct ConfigKey {
|
||||
const ConfigDescription* config;
|
||||
const StringPiece& product;
|
||||
const ConfigDescription* config;
|
||||
const StringPiece& product;
|
||||
};
|
||||
|
||||
bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
|
||||
int cmp = lhs->config.compare(*rhs.config);
|
||||
if (cmp == 0) {
|
||||
cmp = StringPiece(lhs->product).compare(rhs.product);
|
||||
}
|
||||
return cmp < 0;
|
||||
bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs,
|
||||
const ConfigKey& rhs) {
|
||||
int cmp = lhs->config.compare(*rhs.config);
|
||||
if (cmp == 0) {
|
||||
cmp = StringPiece(lhs->product).compare(rhs.product);
|
||||
}
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{ &config, product }, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{&config, product}, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{ &config, product }, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
ResourceConfigValue* ResourceEntry::findOrCreateValue(
|
||||
const ConfigDescription& config, const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{&config, product}, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
ResourceConfigValue* newValue = values.insert(
|
||||
iter, util::make_unique<ResourceConfigValue>(config, product))->get();
|
||||
return newValue;
|
||||
}
|
||||
ResourceConfigValue* newValue =
|
||||
values
|
||||
.insert(iter, util::make_unique<ResourceConfigValue>(config, product))
|
||||
->get();
|
||||
return newValue;
|
||||
}
|
||||
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(
|
||||
const ConfigDescription& config) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
|
||||
auto iter = values.begin();
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
auto iter = values.begin();
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
}
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
|
||||
const std::function<bool(ResourceConfigValue*)>& f) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
for (auto& configValue : values) {
|
||||
if (f(configValue.get())) {
|
||||
results.push_back(configValue.get());
|
||||
}
|
||||
const std::function<bool(ResourceConfigValue*)>& f) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
for (auto& configValue : values) {
|
||||
if (f(configValue.get())) {
|
||||
results.push_back(configValue.get());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default handler for collisions.
|
||||
*
|
||||
* Typically, a weak value will be overridden by a strong value. An existing weak
|
||||
* Typically, a weak value will be overridden by a strong value. An existing
|
||||
* weak
|
||||
* value will not be overridden by an incoming weak value.
|
||||
*
|
||||
* There are some exceptions:
|
||||
*
|
||||
* Attributes: There are two types of Attribute values: USE and DECL.
|
||||
*
|
||||
* USE is anywhere an Attribute is declared without a format, and in a place that would
|
||||
* USE is anywhere an Attribute is declared without a format, and in a place
|
||||
* that would
|
||||
* be legal to declare if the Attribute already existed. This is typically in a
|
||||
* <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
|
||||
* <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
|
||||
* weak.
|
||||
*
|
||||
* DECL is an absolute declaration of an Attribute and specifies an explicit format.
|
||||
* DECL is an absolute declaration of an Attribute and specifies an explicit
|
||||
* format.
|
||||
*
|
||||
* A DECL will override a USE without error. Two DECLs must match in their format for there to be
|
||||
* A DECL will override a USE without error. Two DECLs must match in their
|
||||
* format for there to be
|
||||
* no error.
|
||||
*/
|
||||
ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
Value* existing, Value* incoming) {
|
||||
Attribute* existingAttr = valueCast<Attribute>(existing);
|
||||
Attribute* incomingAttr = valueCast<Attribute>(incoming);
|
||||
if (!incomingAttr) {
|
||||
if (incoming->isWeak()) {
|
||||
// We're trying to add a weak resource but a resource
|
||||
// already exists. Keep the existing.
|
||||
return CollisionResult::kKeepOriginal;
|
||||
} else if (existing->isWeak()) {
|
||||
// Override the weak resource with the new strong resource.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
// The existing and incoming values are strong, this is an error
|
||||
// if the values are not both attributes.
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
if (!existingAttr) {
|
||||
if (existing->isWeak()) {
|
||||
// The existing value is not an attribute and it is weak,
|
||||
// so take the incoming attribute value.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
// The existing value is not an attribute and it is strong,
|
||||
// so the incoming attribute value is an error.
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
assert(incomingAttr && existingAttr);
|
||||
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
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() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal;
|
||||
}
|
||||
|
||||
if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// Any incoming attribute is better than this.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
|
||||
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 CollisionResult::kKeepOriginal;
|
||||
Value* existing, Value* incoming) {
|
||||
Attribute* existingAttr = valueCast<Attribute>(existing);
|
||||
Attribute* incomingAttr = valueCast<Attribute>(incoming);
|
||||
if (!incomingAttr) {
|
||||
if (incoming->isWeak()) {
|
||||
// We're trying to add a weak resource but a resource
|
||||
// already exists. Keep the existing.
|
||||
return CollisionResult::kKeepOriginal;
|
||||
} else if (existing->isWeak()) {
|
||||
// Override the weak resource with the new strong resource.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
// The existing and incoming values are strong, this is an error
|
||||
// if the values are not both attributes.
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
if (!existingAttr) {
|
||||
if (existing->isWeak()) {
|
||||
// The existing value is not an attribute and it is weak,
|
||||
// so take the incoming attribute value.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
// The existing value is not an attribute and it is strong,
|
||||
// so the incoming attribute value is an error.
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
assert(incomingAttr && existingAttr);
|
||||
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
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() ? CollisionResult::kTakeNew
|
||||
: CollisionResult::kKeepOriginal;
|
||||
}
|
||||
|
||||
if (existingAttr->isWeak() &&
|
||||
existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// Any incoming attribute is better than this.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
|
||||
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 CollisionResult::kKeepOriginal;
|
||||
}
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
static constexpr const char* kValidNameChars = "._-";
|
||||
@@ -284,8 +300,8 @@ bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars,
|
||||
resolveValueCollision, diag);
|
||||
return addResourceImpl(name, {}, config, product, std::move(value),
|
||||
kValidNameChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
@@ -294,8 +310,8 @@ bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars,
|
||||
resolveValueCollision, diag);
|
||||
return addResourceImpl(name, resId, config, product, std::move(value),
|
||||
kValidNameChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
@@ -303,31 +319,29 @@ bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
|
||||
return addFileReferenceImpl(name, config, source, path, nullptr,
|
||||
kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
io::IFile* file,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
|
||||
bool ResourceTable::addFileReferenceAllowMangled(
|
||||
const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece& path, io::IFile* file,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReferenceImpl(name, config, source, path, file,
|
||||
kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
io::IFile* file,
|
||||
const char* validChars,
|
||||
IDiagnostics* diag) {
|
||||
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
|
||||
stringPool.makeRef(path));
|
||||
fileRef->setSource(source);
|
||||
fileRef->file = file;
|
||||
return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
|
||||
validChars, resolveValueCollision, diag);
|
||||
bool ResourceTable::addFileReferenceImpl(
|
||||
const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece& path, io::IFile* file,
|
||||
const char* validChars, IDiagnostics* diag) {
|
||||
std::unique_ptr<FileReference> fileRef =
|
||||
util::make_unique<FileReference>(stringPool.makeRef(path));
|
||||
fileRef->setSource(source);
|
||||
fileRef->file = file;
|
||||
return addResourceImpl(name, ResourceId{}, config, StringPiece{},
|
||||
std::move(fileRef), validChars, resolveValueCollision,
|
||||
diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
@@ -335,8 +349,8 @@ bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
|
||||
kValidNameMangledChars, resolveValueCollision, diag);
|
||||
return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
|
||||
kValidNameMangledChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
@@ -345,220 +359,193 @@ bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars,
|
||||
resolveValueCollision, diag);
|
||||
return addResourceImpl(name, id, config, product, std::move(value),
|
||||
kValidNameMangledChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
const char* validChars,
|
||||
const CollisionResolverFunc& conflictResolver,
|
||||
IDiagnostics* diag) {
|
||||
assert(value && "value can't be nullptr");
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
bool ResourceTable::addResourceImpl(
|
||||
const ResourceNameRef& name, const ResourceId& resId,
|
||||
const ConfigDescription& config, const StringPiece& product,
|
||||
std::unique_ptr<Value> value, const char* validChars,
|
||||
const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
|
||||
assert(value && "value can't be nullptr");
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1)
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
auto badCharIter =
|
||||
util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "resource '" << name << "' has invalid entry name '"
|
||||
<< name.entry << "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1) << "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but package '"
|
||||
<< package->name
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id &&
|
||||
package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but package '" << package->name << "' already has ID "
|
||||
<< std::hex << (int)package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but type '" << type->type << "' already has ID "
|
||||
<< std::hex << (int)type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(),
|
||||
entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
|
||||
if (!configValue->value) {
|
||||
// Resource does not exist, add it now.
|
||||
ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
|
||||
if (!configValue->value) {
|
||||
// Resource does not exist, add it now.
|
||||
configValue->value = std::move(value);
|
||||
|
||||
} else {
|
||||
switch (conflictResolver(configValue->value.get(), value.get())) {
|
||||
case CollisionResult::kTakeNew:
|
||||
// Take the incoming value.
|
||||
configValue->value = std::move(value);
|
||||
break;
|
||||
|
||||
} else {
|
||||
switch (conflictResolver(configValue->value.get(), value.get())) {
|
||||
case CollisionResult::kTakeNew:
|
||||
// Take the incoming value.
|
||||
configValue->value = std::move(value);
|
||||
break;
|
||||
case CollisionResult::kConflict:
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << config << "'");
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "resource previously defined here");
|
||||
return false;
|
||||
|
||||
case CollisionResult::kConflict:
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << config << "'");
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "resource previously defined here");
|
||||
return false;
|
||||
|
||||
case CollisionResult::kKeepOriginal:
|
||||
break;
|
||||
}
|
||||
case CollisionResult::kKeepOriginal:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (resId.isValid()) {
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
return true;
|
||||
if (resId.isValid()) {
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
|
||||
bool ResourceTable::setSymbolState(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol, IDiagnostics* diag) {
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol, IDiagnostics* diag) {
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
|
||||
const Symbol& symbol,
|
||||
IDiagnostics* diag) {
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const Symbol& symbol, const char* validChars,
|
||||
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol,
|
||||
const char* validChars,
|
||||
IDiagnostics* diag) {
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1)
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
auto badCharIter =
|
||||
util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "resource '" << name << "' has invalid entry name '"
|
||||
<< name.entry << "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1) << "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but package '"
|
||||
<< package->name
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
ResourceTablePackage* package = findOrCreatePackage(name.package);
|
||||
if (resId.isValid() && package->id &&
|
||||
package->id.value() != resId.packageId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but package '" << package->name << "' already has ID "
|
||||
<< std::hex << (int)package->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
ResourceTableType* type = package->findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but type '" << type->type << "' already has ID "
|
||||
<< std::hex << (int)type->id.value() << std::dec);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(), entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
|
||||
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(package->id.value(), type->id.value(),
|
||||
entry->id.value()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resId.isValid()) {
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
if (resId.isValid()) {
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
}
|
||||
|
||||
// Only mark the type state as public, it doesn't care about being private.
|
||||
if (symbol.state == SymbolState::kPublic) {
|
||||
type->symbolStatus.state = SymbolState::kPublic;
|
||||
}
|
||||
// Only mark the type state as public, it doesn't care about being private.
|
||||
if (symbol.state == SymbolState::kPublic) {
|
||||
type->symbolStatus.state = SymbolState::kPublic;
|
||||
}
|
||||
|
||||
if (symbol.state == SymbolState::kUndefined &&
|
||||
entry->symbolStatus.state != SymbolState::kUndefined) {
|
||||
// We can't undefine a symbol (remove its visibility). Ignore.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (symbol.state == SymbolState::kPrivate &&
|
||||
entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
// We can't downgrade public to private. Ignore.
|
||||
return true;
|
||||
}
|
||||
|
||||
entry->symbolStatus = std::move(symbol);
|
||||
if (symbol.state == SymbolState::kUndefined &&
|
||||
entry->symbolStatus.state != SymbolState::kUndefined) {
|
||||
// We can't undefine a symbol (remove its visibility). Ignore.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (symbol.state == SymbolState::kPrivate &&
|
||||
entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
// We can't downgrade public to private. Ignore.
|
||||
return true;
|
||||
}
|
||||
|
||||
entry->symbolStatus = std::move(symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<ResourceTable::SearchResult>
|
||||
ResourceTable::findResource(const ResourceNameRef& name) {
|
||||
ResourceTablePackage* package = findPackage(name.package);
|
||||
if (!package) {
|
||||
return {};
|
||||
}
|
||||
Maybe<ResourceTable::SearchResult> ResourceTable::findResource(
|
||||
const ResourceNameRef& name) {
|
||||
ResourceTablePackage* package = findPackage(name.package);
|
||||
if (!package) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourceTableType* type = package->findType(name.type);
|
||||
if (!type) {
|
||||
return {};
|
||||
}
|
||||
ResourceTableType* type = package->findType(name.type);
|
||||
if (!type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourceEntry* entry = type->findEntry(name.entry);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
return SearchResult{ package, type, entry };
|
||||
ResourceEntry* entry = type->findEntry(name.entry);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
return SearchResult{package, type, entry};
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -37,42 +37,43 @@
|
||||
namespace aapt {
|
||||
|
||||
enum class SymbolState {
|
||||
kUndefined,
|
||||
kPrivate,
|
||||
kPublic,
|
||||
kUndefined,
|
||||
kPrivate,
|
||||
kPublic,
|
||||
};
|
||||
|
||||
/**
|
||||
* The Public status of a resource.
|
||||
*/
|
||||
struct Symbol {
|
||||
SymbolState state = SymbolState::kUndefined;
|
||||
Source source;
|
||||
std::string comment;
|
||||
SymbolState state = SymbolState::kUndefined;
|
||||
Source source;
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
class ResourceConfigValue {
|
||||
public:
|
||||
/**
|
||||
* The configuration for which this value is defined.
|
||||
*/
|
||||
const ConfigDescription config;
|
||||
public:
|
||||
/**
|
||||
* The configuration for which this value is defined.
|
||||
*/
|
||||
const ConfigDescription config;
|
||||
|
||||
/**
|
||||
* The product for which this value is defined.
|
||||
*/
|
||||
const std::string product;
|
||||
/**
|
||||
* The product for which this value is defined.
|
||||
*/
|
||||
const std::string product;
|
||||
|
||||
/**
|
||||
* The actual Value.
|
||||
*/
|
||||
std::unique_ptr<Value> value;
|
||||
/**
|
||||
* The actual Value.
|
||||
*/
|
||||
std::unique_ptr<Value> value;
|
||||
|
||||
ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) :
|
||||
config(config), product(product.toString()) { }
|
||||
ResourceConfigValue(const ConfigDescription& config,
|
||||
const StringPiece& product)
|
||||
: config(config), product(product.toString()) {}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,42 +81,44 @@ private:
|
||||
* varying values for each defined configuration.
|
||||
*/
|
||||
class ResourceEntry {
|
||||
public:
|
||||
/**
|
||||
* The name of the resource. Immutable, as
|
||||
* this determines the order of this resource
|
||||
* when doing lookups.
|
||||
*/
|
||||
const std::string name;
|
||||
public:
|
||||
/**
|
||||
* The name of the resource. Immutable, as
|
||||
* this determines the order of this resource
|
||||
* when doing lookups.
|
||||
*/
|
||||
const std::string name;
|
||||
|
||||
/**
|
||||
* The entry ID for this resource.
|
||||
*/
|
||||
Maybe<uint16_t> id;
|
||||
/**
|
||||
* The entry ID for this resource.
|
||||
*/
|
||||
Maybe<uint16_t> id;
|
||||
|
||||
/**
|
||||
* Whether this resource is public (and must maintain the same entry ID across builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
/**
|
||||
* Whether this resource is public (and must maintain the same entry ID across
|
||||
* builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
|
||||
/**
|
||||
* The resource's values for each configuration.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> values;
|
||||
/**
|
||||
* The resource's values for each configuration.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> values;
|
||||
|
||||
explicit ResourceEntry(const StringPiece& name) : name(name.toString()) { }
|
||||
explicit ResourceEntry(const StringPiece& name) : name(name.toString()) {}
|
||||
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config);
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product);
|
||||
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
|
||||
std::vector<ResourceConfigValue*> findValuesIf(
|
||||
const std::function<bool(ResourceConfigValue*)>& f);
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config);
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
std::vector<ResourceConfigValue*> findAllValues(
|
||||
const ConfigDescription& config);
|
||||
std::vector<ResourceConfigValue*> findValuesIf(
|
||||
const std::function<bool(ResourceConfigValue*)>& f);
|
||||
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -123,58 +126,53 @@ private:
|
||||
* for this type.
|
||||
*/
|
||||
class ResourceTableType {
|
||||
public:
|
||||
/**
|
||||
* The logical type of resource (string, drawable, layout, etc.).
|
||||
*/
|
||||
const ResourceType type;
|
||||
public:
|
||||
/**
|
||||
* The logical type of resource (string, drawable, layout, etc.).
|
||||
*/
|
||||
const ResourceType type;
|
||||
|
||||
/**
|
||||
* The type ID for this resource.
|
||||
*/
|
||||
Maybe<uint8_t> id;
|
||||
/**
|
||||
* The type ID for this resource.
|
||||
*/
|
||||
Maybe<uint8_t> id;
|
||||
|
||||
/**
|
||||
* Whether this type is public (and must maintain the same
|
||||
* type ID across builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
/**
|
||||
* Whether this type is public (and must maintain the same
|
||||
* type ID across builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
|
||||
/**
|
||||
* List of resources for this type.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceEntry>> entries;
|
||||
/**
|
||||
* List of resources for this type.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceEntry>> entries;
|
||||
|
||||
explicit ResourceTableType(const ResourceType type) : type(type) { }
|
||||
explicit ResourceTableType(const ResourceType type) : type(type) {}
|
||||
|
||||
ResourceEntry* findEntry(const StringPiece& name);
|
||||
ResourceEntry* findOrCreateEntry(const StringPiece& name);
|
||||
ResourceEntry* findEntry(const StringPiece& name);
|
||||
ResourceEntry* findOrCreateEntry(const StringPiece& name);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
|
||||
};
|
||||
|
||||
enum class PackageType {
|
||||
System,
|
||||
Vendor,
|
||||
App,
|
||||
Dynamic
|
||||
};
|
||||
enum class PackageType { System, Vendor, App, Dynamic };
|
||||
|
||||
class ResourceTablePackage {
|
||||
public:
|
||||
PackageType type = PackageType::App;
|
||||
Maybe<uint8_t> id;
|
||||
std::string name;
|
||||
public:
|
||||
PackageType type = PackageType::App;
|
||||
Maybe<uint8_t> id;
|
||||
std::string name;
|
||||
|
||||
std::vector<std::unique_ptr<ResourceTableType>> types;
|
||||
std::vector<std::unique_ptr<ResourceTableType>> types;
|
||||
|
||||
ResourceTablePackage() = default;
|
||||
ResourceTableType* findType(ResourceType type);
|
||||
ResourceTableType* findOrCreateType(const ResourceType type);
|
||||
ResourceTablePackage() = default;
|
||||
ResourceTableType* findType(ResourceType type);
|
||||
ResourceTableType* findOrCreateType(const ResourceType type);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,140 +180,129 @@ private:
|
||||
* flattened into a binary resource table (resources.arsc).
|
||||
*/
|
||||
class ResourceTable {
|
||||
public:
|
||||
ResourceTable() = default;
|
||||
public:
|
||||
ResourceTable() = default;
|
||||
|
||||
enum class CollisionResult {
|
||||
kKeepOriginal,
|
||||
kConflict,
|
||||
kTakeNew
|
||||
};
|
||||
enum class CollisionResult { kKeepOriginal, kConflict, kTakeNew };
|
||||
|
||||
using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
|
||||
using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
|
||||
|
||||
/**
|
||||
* When a collision of resources occurs, this method decides which value to keep.
|
||||
*/
|
||||
static CollisionResult resolveValueCollision(Value* existing, Value* incoming);
|
||||
/**
|
||||
* When a collision of resources occurs, this method decides which value to
|
||||
* keep.
|
||||
*/
|
||||
static CollisionResult resolveValueCollision(Value* existing,
|
||||
Value* incoming);
|
||||
|
||||
bool addResource(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const StringPiece& product, std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResource(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
bool addResource(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const ConfigDescription& config, const StringPiece& product,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
IDiagnostics* diag);
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const Source& source,
|
||||
const StringPiece& path, IDiagnostics* diag);
|
||||
|
||||
bool addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
io::IFile* file,
|
||||
IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* Same as addResource, but doesn't verify the validity of the name. This is used
|
||||
* when loading resources from an existing binary resource table that may have mangled
|
||||
* names.
|
||||
*/
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& id,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolState(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol,
|
||||
bool addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path, io::IFile* file,
|
||||
IDiagnostics* diag);
|
||||
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
ResourceTableType* type;
|
||||
ResourceEntry* entry;
|
||||
};
|
||||
/**
|
||||
* Same as addResource, but doesn't verify the validity of the name. This is
|
||||
* used
|
||||
* when loading resources from an existing binary resource table that may have
|
||||
* mangled
|
||||
* names.
|
||||
*/
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
Maybe<SearchResult> findResource(const ResourceNameRef& name);
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& id,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* The string pool used by this resource table. Values that reference strings must use
|
||||
* this pool to create their strings.
|
||||
*
|
||||
* NOTE: `stringPool` must come before `packages` so that it is destroyed after.
|
||||
* When `string pool` references are destroyed (as they will be when `packages`
|
||||
* is destroyed), they decrement a refCount, which would cause invalid
|
||||
* memory access if the pool was already destroyed.
|
||||
*/
|
||||
StringPool stringPool;
|
||||
bool setSymbolState(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const Symbol& symbol, IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* The list of packages in this table, sorted alphabetically by package name.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
|
||||
bool setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& resId, const Symbol& symbol,
|
||||
IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* Returns the package struct with the given name, or nullptr if such a package does not
|
||||
* exist. The empty string is a valid package and typically is used to represent the
|
||||
* 'current' package before it is known to the ResourceTable.
|
||||
*/
|
||||
ResourceTablePackage* findPackage(const StringPiece& name);
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
ResourceTableType* type;
|
||||
ResourceEntry* entry;
|
||||
};
|
||||
|
||||
ResourceTablePackage* findPackageById(uint8_t id);
|
||||
Maybe<SearchResult> findResource(const ResourceNameRef& name);
|
||||
|
||||
ResourceTablePackage* createPackage(const StringPiece& name, Maybe<uint8_t> id = {});
|
||||
/**
|
||||
* The string pool used by this resource table. Values that reference strings
|
||||
* must use
|
||||
* this pool to create their strings.
|
||||
*
|
||||
* NOTE: `stringPool` must come before `packages` so that it is destroyed
|
||||
* after.
|
||||
* When `string pool` references are destroyed (as they will be when
|
||||
* `packages`
|
||||
* is destroyed), they decrement a refCount, which would cause invalid
|
||||
* memory access if the pool was already destroyed.
|
||||
*/
|
||||
StringPool stringPool;
|
||||
|
||||
private:
|
||||
ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
|
||||
/**
|
||||
* The list of packages in this table, sorted alphabetically by package name.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceTablePackage>> packages;
|
||||
|
||||
bool addResourceImpl(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
const char* validChars,
|
||||
const CollisionResolverFunc& conflictResolver,
|
||||
IDiagnostics* diag);
|
||||
/**
|
||||
* Returns the package struct with the given name, or nullptr if such a
|
||||
* package does not
|
||||
* exist. The empty string is a valid package and typically is used to
|
||||
* represent the
|
||||
* 'current' package before it is known to the ResourceTable.
|
||||
*/
|
||||
ResourceTablePackage* findPackage(const StringPiece& name);
|
||||
|
||||
bool addFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
io::IFile* file,
|
||||
const char* validChars,
|
||||
IDiagnostics* diag);
|
||||
ResourceTablePackage* findPackageById(uint8_t id);
|
||||
|
||||
bool setSymbolStateImpl(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
const Symbol& symbol,
|
||||
const char* validChars,
|
||||
ResourceTablePackage* createPackage(const StringPiece& name,
|
||||
Maybe<uint8_t> id = {});
|
||||
|
||||
private:
|
||||
ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
|
||||
|
||||
bool addResourceImpl(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product, std::unique_ptr<Value> value,
|
||||
const char* validChars,
|
||||
const CollisionResolverFunc& conflictResolver,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source, const StringPiece& path,
|
||||
io::IFile* file, const char* validChars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
|
||||
bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const Symbol& symbol, const char* validChars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TABLE_H
|
||||
#endif // AAPT_RESOURCE_TABLE_H
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
@@ -27,124 +27,113 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
ResourceTable table;
|
||||
ResourceTable table;
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
test::parseNameOrDie("android:id/hey,there"),
|
||||
ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_FALSE(table.addResource(
|
||||
test::parseNameOrDie("android:id/hey,there"), ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
test::parseNameOrDie("android:id/hey:there"),
|
||||
ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_FALSE(table.addResource(
|
||||
test::parseNameOrDie("android:id/hey:there"), ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
test::getDiagnostics()));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddOneResource) {
|
||||
ResourceTable table;
|
||||
ResourceTable table;
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/id"),
|
||||
ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/id"), ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddMultipleResources) {
|
||||
ResourceTable table;
|
||||
ResourceTable table;
|
||||
|
||||
ConfigDescription config;
|
||||
ConfigDescription languageConfig;
|
||||
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
|
||||
ConfigDescription config;
|
||||
ConfigDescription languageConfig;
|
||||
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/layout_width"),
|
||||
config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/layout_width"), config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/id"),
|
||||
config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/id"), config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:string/ok"),
|
||||
config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:string/ok"), config, "",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:string/ok"),
|
||||
languageConfig, "",
|
||||
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
|
||||
.setSource("test/path/file.xml", 20u)
|
||||
.build(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:string/ok"), languageConfig, "",
|
||||
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
|
||||
.setSource("test/path/file.xml", 20u)
|
||||
.build(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok"));
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, "android:string/ok",
|
||||
languageConfig));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
|
||||
ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok"));
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(
|
||||
&table, "android:string/ok", languageConfig));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
ResourceTable table;
|
||||
ResourceTable table;
|
||||
|
||||
ASSERT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/foo"),
|
||||
ConfigDescription{}, "",
|
||||
util::make_unique<Attribute>(true),
|
||||
test::getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
|
||||
util::make_unique<Attribute>(true), test::getDiagnostics()));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
ASSERT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/foo"),
|
||||
ConfigDescription{}, "",
|
||||
util::make_unique<Attribute>(false),
|
||||
test::getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(
|
||||
test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
|
||||
util::make_unique<Attribute>(false), test::getDiagnostics()));
|
||||
|
||||
attr = test::getValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_FALSE(attr->isWeak());
|
||||
attr = test::getValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_FALSE(attr->isWeak());
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, ProductVaryingValues) {
|
||||
ResourceTable table;
|
||||
ResourceTable table;
|
||||
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
|
||||
test::parseConfigOrDie("land"), "tablet",
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
|
||||
test::parseConfigOrDie("land"), "phone",
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
|
||||
test::parseConfigOrDie("land"), "tablet",
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
|
||||
test::parseConfigOrDie("land"), "phone",
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"),
|
||||
"tablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"),
|
||||
"phone"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
|
||||
&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"), "tablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
|
||||
&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"), "phone"));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> sr = table.findResource(
|
||||
test::parseNameOrDie("android:string/foo"));
|
||||
AAPT_ASSERT_TRUE(sr);
|
||||
std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
|
||||
test::parseConfigOrDie("land"));
|
||||
ASSERT_EQ(2u, values.size());
|
||||
EXPECT_EQ(std::string("phone"), values[0]->product);
|
||||
EXPECT_EQ(std::string("tablet"), values[1]->product);
|
||||
Maybe<ResourceTable::SearchResult> sr =
|
||||
table.findResource(test::parseNameOrDie("android:string/foo"));
|
||||
AAPT_ASSERT_TRUE(sr);
|
||||
std::vector<ResourceConfigValue*> values =
|
||||
sr.value().entry->findAllValues(test::parseConfigOrDie("land"));
|
||||
ASSERT_EQ(2u, values.size());
|
||||
EXPECT_EQ(std::string("phone"), values[0]->product);
|
||||
EXPECT_EQ(std::string("tablet"), values[1]->product);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,15 +41,18 @@ bool extractResourceName(const StringPiece& str, StringPiece* outPackage,
|
||||
StringPiece* outType, StringPiece* outEntry);
|
||||
|
||||
/**
|
||||
* Returns true if the string was parsed as a resource name ([*][package:]type/name), with
|
||||
* `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
|
||||
* Returns true if the string was parsed as a resource name
|
||||
* ([*][package:]type/name), with
|
||||
* `outResource` set to the parsed resource name and `outPrivate` set to true if
|
||||
* a '*' prefix
|
||||
* was present.
|
||||
*/
|
||||
bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource,
|
||||
bool* outPrivate = nullptr);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
|
||||
* Returns true if the string was parsed as a reference
|
||||
* (@[+][package:]type/name), with
|
||||
* `outReference` set to the parsed reference.
|
||||
*
|
||||
* If '+' was present in the reference, `outCreate` is set to true.
|
||||
@@ -59,28 +62,34 @@ bool parseReference(const StringPiece& str, ResourceNameRef* outReference,
|
||||
bool* outCreate = nullptr, bool* outPrivate = nullptr);
|
||||
|
||||
/*
|
||||
* Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
|
||||
* Returns true if the string is in the form of a resource reference
|
||||
* (@[+][package:]type/name).
|
||||
*/
|
||||
bool isReference(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
|
||||
* Returns true if the string was parsed as an attribute reference
|
||||
* (?[package:][type/]name),
|
||||
* with `outReference` set to the parsed reference.
|
||||
*/
|
||||
bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference);
|
||||
bool parseAttributeReference(const StringPiece& str,
|
||||
ResourceNameRef* outReference);
|
||||
|
||||
/**
|
||||
* Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
|
||||
* Returns true if the string is in the form of an attribute
|
||||
* reference(?[package:][type/]name).
|
||||
*/
|
||||
bool isAttributeReference(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
|
||||
*/
|
||||
Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name);
|
||||
Maybe<ResourceName> toResourceName(
|
||||
const android::ResTable::resource_name& name);
|
||||
|
||||
/**
|
||||
* Returns a boolean value if the string is equal to TRUE, true, True, FALSE, false, or False.
|
||||
* Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
|
||||
* false, or False.
|
||||
*/
|
||||
Maybe<bool> parseBool(const StringPiece& str);
|
||||
|
||||
@@ -100,18 +109,22 @@ Maybe<ResourceId> parseResourceId(const StringPiece& str);
|
||||
Maybe<int> parseSdkVersion(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a Reference, or None Maybe instance if the string `str` was parsed as a
|
||||
* Returns a Reference, or None Maybe instance if the string `str` was parsed as
|
||||
* a
|
||||
* valid reference to a style.
|
||||
* The format for a style parent is slightly more flexible than a normal reference:
|
||||
* The format for a style parent is slightly more flexible than a normal
|
||||
* reference:
|
||||
*
|
||||
* @[package:]style/<entry> or
|
||||
* ?[package:]style/<entry> or
|
||||
* <package>:[style/]<entry>
|
||||
*/
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError);
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece& str,
|
||||
std::string* outError);
|
||||
|
||||
/*
|
||||
* Returns a Reference if the string `str` was parsed as a valid XML attribute name.
|
||||
* Returns a Reference if the string `str` was parsed as a valid XML attribute
|
||||
* name.
|
||||
* The valid format for an XML attribute name is:
|
||||
*
|
||||
* package:entry
|
||||
@@ -119,14 +132,18 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string*
|
||||
Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a Reference object if the string was parsed as a resource or attribute reference,
|
||||
* ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if
|
||||
* Returns a Reference object if the string was parsed as a resource or
|
||||
* attribute reference,
|
||||
* ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true
|
||||
* if
|
||||
* the '+' was present in the string.
|
||||
*/
|
||||
std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate = nullptr);
|
||||
std::unique_ptr<Reference> tryParseReference(const StringPiece& str,
|
||||
bool* outCreate = nullptr);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string
|
||||
* was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str);
|
||||
@@ -138,13 +155,15 @@ std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was parsed
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was parsed
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str);
|
||||
@@ -156,45 +175,51 @@ std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
|
||||
const StringPiece& str);
|
||||
/*
|
||||
* Try to convert a string to an Item for the given attribute. The attribute will
|
||||
* Try to convert a string to an Item for the given attribute. The attribute
|
||||
* will
|
||||
* restrict what values the string can be converted to.
|
||||
* The callback function onCreateReference is called when the parsed item is a
|
||||
* reference to an ID that must be created (@+id/foo).
|
||||
*/
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
const StringPiece& value, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
const StringPiece& value, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t typeMask,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
const StringPiece& value, uint32_t typeMask,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
|
||||
uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
/**
|
||||
* Returns a string path suitable for use within an APK. The path will look like:
|
||||
* Returns a string path suitable for use within an APK. The path will look
|
||||
* like:
|
||||
*
|
||||
* res/type[-config]/<name>.<ext>
|
||||
*
|
||||
* Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package
|
||||
* Then name may be mangled if a NameMangler is supplied (can be nullptr) and
|
||||
* the package
|
||||
* requires mangling.
|
||||
*/
|
||||
std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler);
|
||||
std::string buildResourceFileName(const ResourceFile& resFile,
|
||||
const NameMangler* mangler);
|
||||
|
||||
} // namespace ResourceUtils
|
||||
} // namespace aapt
|
||||
} // namespace ResourceUtils
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_RESOURCEUTILS_H */
|
||||
|
||||
@@ -14,179 +14,188 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Resource.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "Resource.h"
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceUtilsTest, ParseBool) {
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true"));
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE"));
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False"));
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true"));
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE"));
|
||||
EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE"));
|
||||
EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False"));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseResourceName) {
|
||||
ResourceNameRef actual;
|
||||
bool actualPriv = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual, &actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
ResourceNameRef actual;
|
||||
bool actualPriv = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual,
|
||||
&actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
EXPECT_TRUE(
|
||||
ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual, &actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_TRUE(actualPriv);
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual,
|
||||
&actualPriv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_TRUE(actualPriv);
|
||||
|
||||
EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
|
||||
EXPECT_FALSE(
|
||||
ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
|
||||
ResourceNameRef expected({}, ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
ResourceNameRef expected({}, ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
|
||||
ResourceNameRef expected("android", ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
ResourceNameRef expected("android", ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
|
||||
ResourceNameRef expected("android", ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
ResourceNameRef expected("android", ResourceType::kColor, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t",
|
||||
&actual, &create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
|
||||
ResourceNameRef expected("android", ResourceType::kId, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
ResourceNameRef expected("android", ResourceType::kId, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParsePrivateReference) {
|
||||
ResourceNameRef expected("android", ResourceType::kId, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
ResourceNameRef expected("android", ResourceType::kId, "foo");
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual,
|
||||
&create, &privateRef));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseAttributeReferences) {
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo"));
|
||||
EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo"));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, FailParseIncompleteReference) {
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/"));
|
||||
EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo"));
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseStyleParentReference) {
|
||||
const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, "foo");
|
||||
const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
|
||||
const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle,
|
||||
"foo");
|
||||
const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
|
||||
|
||||
std::string errStr;
|
||||
Maybe<Reference> ref = ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
std::string errStr;
|
||||
Maybe<Reference> ref =
|
||||
ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
ref = ResourceUtils::parseStyleParentReference("foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
EXPECT_TRUE(ref.value().privateReference);
|
||||
ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
EXPECT_TRUE(ref.value().privateReference);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseEmptyFlag) {
|
||||
std::unique_ptr<Attribute> attr = test::AttributeBuilder(false)
|
||||
.setTypeMask(android::ResTable_map::TYPE_FLAGS)
|
||||
.addItem("one", 0x01)
|
||||
.addItem("two", 0x02)
|
||||
.build();
|
||||
std::unique_ptr<Attribute> attr =
|
||||
test::AttributeBuilder(false)
|
||||
.setTypeMask(android::ResTable_map::TYPE_FLAGS)
|
||||
.addItem("one", 0x01)
|
||||
.addItem("two", 0x02)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), "");
|
||||
ASSERT_NE(nullptr, result);
|
||||
EXPECT_EQ(0u, result->value.data);
|
||||
std::unique_ptr<BinaryPrimitive> result =
|
||||
ResourceUtils::tryParseFlagSymbol(attr.get(), "");
|
||||
ASSERT_NE(nullptr, result);
|
||||
EXPECT_EQ(0u, result->value.data);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,8 +23,8 @@
|
||||
#include "io/File.h"
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <array>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
@@ -40,84 +40,64 @@ struct RawValueVisitor;
|
||||
* but it is the simplest strategy.
|
||||
*/
|
||||
struct Value {
|
||||
virtual ~Value() = default;
|
||||
virtual ~Value() = default;
|
||||
|
||||
/**
|
||||
* Whether this value is weak and can be overridden without
|
||||
* warning or error. Default is false.
|
||||
*/
|
||||
bool isWeak() const {
|
||||
return mWeak;
|
||||
}
|
||||
/**
|
||||
* Whether this value is weak and can be overridden without
|
||||
* warning or error. Default is false.
|
||||
*/
|
||||
bool isWeak() const { return mWeak; }
|
||||
|
||||
void setWeak(bool val) {
|
||||
mWeak = val;
|
||||
}
|
||||
void setWeak(bool val) { mWeak = val; }
|
||||
|
||||
// Whether the value is marked as translateable.
|
||||
// This does not persist when flattened.
|
||||
// It is only used during compilation phase.
|
||||
void setTranslateable(bool val) {
|
||||
mTranslateable = val;
|
||||
}
|
||||
// Whether the value is marked as translateable.
|
||||
// This does not persist when flattened.
|
||||
// It is only used during compilation phase.
|
||||
void setTranslateable(bool val) { mTranslateable = val; }
|
||||
|
||||
// Default true.
|
||||
bool isTranslateable() const {
|
||||
return mTranslateable;
|
||||
}
|
||||
// Default true.
|
||||
bool isTranslateable() const { return mTranslateable; }
|
||||
|
||||
/**
|
||||
* Returns the source where this value was defined.
|
||||
*/
|
||||
const Source& getSource() const {
|
||||
return mSource;
|
||||
}
|
||||
/**
|
||||
* Returns the source where this value was defined.
|
||||
*/
|
||||
const Source& getSource() const { return mSource; }
|
||||
|
||||
void setSource(const Source& source) {
|
||||
mSource = source;
|
||||
}
|
||||
void setSource(const Source& source) { mSource = source; }
|
||||
|
||||
void setSource(Source&& source) {
|
||||
mSource = std::move(source);
|
||||
}
|
||||
void setSource(Source&& source) { mSource = std::move(source); }
|
||||
|
||||
/**
|
||||
* Returns the comment that was associated with this resource.
|
||||
*/
|
||||
const std::string& getComment() const {
|
||||
return mComment;
|
||||
}
|
||||
/**
|
||||
* Returns the comment that was associated with this resource.
|
||||
*/
|
||||
const std::string& getComment() const { return mComment; }
|
||||
|
||||
void setComment(const StringPiece& str) {
|
||||
mComment = str.toString();
|
||||
}
|
||||
void setComment(const StringPiece& str) { mComment = str.toString(); }
|
||||
|
||||
void setComment(std::string&& str) {
|
||||
mComment = std::move(str);
|
||||
}
|
||||
void setComment(std::string&& str) { mComment = std::move(str); }
|
||||
|
||||
virtual bool equals(const Value* value) const = 0;
|
||||
virtual bool equals(const Value* value) const = 0;
|
||||
|
||||
/**
|
||||
* Calls the appropriate overload of ValueVisitor.
|
||||
*/
|
||||
virtual void accept(RawValueVisitor* visitor) = 0;
|
||||
/**
|
||||
* Calls the appropriate overload of ValueVisitor.
|
||||
*/
|
||||
virtual void accept(RawValueVisitor* visitor) = 0;
|
||||
|
||||
/**
|
||||
* Clone the value.
|
||||
*/
|
||||
virtual Value* clone(StringPool* newPool) const = 0;
|
||||
/**
|
||||
* Clone the value.
|
||||
*/
|
||||
virtual Value* clone(StringPool* newPool) const = 0;
|
||||
|
||||
/**
|
||||
* Human readable printout of this value.
|
||||
*/
|
||||
virtual void print(std::ostream* out) const = 0;
|
||||
/**
|
||||
* Human readable printout of this value.
|
||||
*/
|
||||
virtual void print(std::ostream* out) const = 0;
|
||||
|
||||
protected:
|
||||
Source mSource;
|
||||
std::string mComment;
|
||||
bool mWeak = false;
|
||||
bool mTranslateable = true;
|
||||
protected:
|
||||
Source mSource;
|
||||
std::string mComment;
|
||||
bool mWeak = false;
|
||||
bool mTranslateable = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -125,23 +105,24 @@ protected:
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseValue : public Value {
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A resource item with a single value. This maps to android::ResTable_entry.
|
||||
*/
|
||||
struct Item : public Value {
|
||||
/**
|
||||
* Clone the Item.
|
||||
*/
|
||||
virtual Item* clone(StringPool* newPool) const override = 0;
|
||||
/**
|
||||
* Clone the Item.
|
||||
*/
|
||||
virtual Item* clone(StringPool* newPool) const override = 0;
|
||||
|
||||
/**
|
||||
* Fills in an android::Res_value structure with this Item's binary representation.
|
||||
* Returns false if an error occurred.
|
||||
*/
|
||||
virtual bool flatten(android::Res_value* outValue) const = 0;
|
||||
/**
|
||||
* Fills in an android::Res_value structure with this Item's binary
|
||||
* representation.
|
||||
* Returns false if an error occurred.
|
||||
*/
|
||||
virtual bool flatten(android::Res_value* outValue) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,35 +130,37 @@ struct Item : public Value {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseItem : public Item {
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
|
||||
* A reference to another resource. This maps to
|
||||
* android::Res_value::TYPE_REFERENCE.
|
||||
*
|
||||
* A reference can be symbolic (with the name set to a valid resource name) or be
|
||||
* A reference can be symbolic (with the name set to a valid resource name) or
|
||||
* be
|
||||
* numeric (the id is set to a valid resource ID).
|
||||
*/
|
||||
struct Reference : public BaseItem<Reference> {
|
||||
enum class Type {
|
||||
kResource,
|
||||
kAttribute,
|
||||
};
|
||||
enum class Type {
|
||||
kResource,
|
||||
kAttribute,
|
||||
};
|
||||
|
||||
Maybe<ResourceName> name;
|
||||
Maybe<ResourceId> id;
|
||||
Reference::Type referenceType;
|
||||
bool privateReference = false;
|
||||
Maybe<ResourceName> name;
|
||||
Maybe<ResourceId> id;
|
||||
Reference::Type referenceType;
|
||||
bool privateReference = false;
|
||||
|
||||
Reference();
|
||||
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
|
||||
explicit Reference(const ResourceId& i, Type type = Type::kResource);
|
||||
explicit Reference(const ResourceNameRef& n, const ResourceId& i);
|
||||
Reference();
|
||||
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
|
||||
explicit Reference(const ResourceId& i, Type type = Type::kResource);
|
||||
explicit Reference(const ResourceNameRef& n, const ResourceId& i);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
Reference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
Reference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
bool operator<(const Reference&, const Reference&);
|
||||
@@ -187,11 +170,11 @@ bool operator==(const Reference&, const Reference&);
|
||||
* An ID resource. Has no real value, just a place holder.
|
||||
*/
|
||||
struct Id : public BaseItem<Id> {
|
||||
Id() { mWeak = true; }
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* out) const override;
|
||||
Id* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
Id() { mWeak = true; }
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* out) const override;
|
||||
Id* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -200,164 +183,157 @@ struct Id : public BaseItem<Id> {
|
||||
* end up in the final resource table.
|
||||
*/
|
||||
struct RawString : public BaseItem<RawString> {
|
||||
StringPool::Ref value;
|
||||
StringPool::Ref value;
|
||||
|
||||
explicit RawString(const StringPool::Ref& ref);
|
||||
explicit RawString(const StringPool::Ref& ref);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
RawString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
RawString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct String : public BaseItem<String> {
|
||||
StringPool::Ref value;
|
||||
StringPool::Ref value;
|
||||
|
||||
explicit String(const StringPool::Ref& ref);
|
||||
explicit String(const StringPool::Ref& ref);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
String* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
String* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct StyledString : public BaseItem<StyledString> {
|
||||
StringPool::StyleRef value;
|
||||
StringPool::StyleRef value;
|
||||
|
||||
explicit StyledString(const StringPool::StyleRef& ref);
|
||||
explicit StyledString(const StringPool::StyleRef& ref);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
StyledString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
StyledString* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct FileReference : public BaseItem<FileReference> {
|
||||
StringPool::Ref path;
|
||||
StringPool::Ref path;
|
||||
|
||||
/**
|
||||
* A handle to the file object from which this file can be read.
|
||||
*/
|
||||
io::IFile* file = nullptr;
|
||||
/**
|
||||
* A handle to the file object from which this file can be read.
|
||||
*/
|
||||
io::IFile* file = nullptr;
|
||||
|
||||
FileReference() = default;
|
||||
explicit FileReference(const StringPool::Ref& path);
|
||||
FileReference() = default;
|
||||
explicit FileReference(const StringPool::Ref& path);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
FileReference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
FileReference* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents any other android::Res_value.
|
||||
*/
|
||||
struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
|
||||
android::Res_value value;
|
||||
android::Res_value value;
|
||||
|
||||
BinaryPrimitive() = default;
|
||||
explicit BinaryPrimitive(const android::Res_value& val);
|
||||
BinaryPrimitive(uint8_t dataType, uint32_t data);
|
||||
BinaryPrimitive() = default;
|
||||
explicit BinaryPrimitive(const android::Res_value& val);
|
||||
BinaryPrimitive(uint8_t dataType, uint32_t data);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
BinaryPrimitive* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
bool flatten(android::Res_value* outValue) const override;
|
||||
BinaryPrimitive* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Attribute : public BaseValue<Attribute> {
|
||||
struct Symbol {
|
||||
Reference symbol;
|
||||
uint32_t value;
|
||||
};
|
||||
struct Symbol {
|
||||
Reference symbol;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
uint32_t typeMask;
|
||||
int32_t minInt;
|
||||
int32_t maxInt;
|
||||
std::vector<Symbol> symbols;
|
||||
uint32_t typeMask;
|
||||
int32_t minInt;
|
||||
int32_t maxInt;
|
||||
std::vector<Symbol> symbols;
|
||||
|
||||
explicit Attribute(bool w, uint32_t t = 0u);
|
||||
explicit Attribute(bool w, uint32_t t = 0u);
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
Attribute* clone(StringPool* newPool) const override;
|
||||
void printMask(std::ostream* out) const;
|
||||
void print(std::ostream* out) const override;
|
||||
bool matches(const Item* item, DiagMessage* outMsg) const;
|
||||
bool equals(const Value* value) const override;
|
||||
Attribute* clone(StringPool* newPool) const override;
|
||||
void printMask(std::ostream* out) const;
|
||||
void print(std::ostream* out) const override;
|
||||
bool matches(const Item* item, DiagMessage* outMsg) const;
|
||||
};
|
||||
|
||||
struct Style : public BaseValue<Style> {
|
||||
struct Entry {
|
||||
Reference key;
|
||||
std::unique_ptr<Item> value;
|
||||
};
|
||||
struct Entry {
|
||||
Reference key;
|
||||
std::unique_ptr<Item> value;
|
||||
};
|
||||
|
||||
Maybe<Reference> parent;
|
||||
Maybe<Reference> parent;
|
||||
|
||||
/**
|
||||
* If set to true, the parent was auto inferred from the
|
||||
* style's name.
|
||||
*/
|
||||
bool parentInferred = false;
|
||||
/**
|
||||
* If set to true, the parent was auto inferred from the
|
||||
* style's name.
|
||||
*/
|
||||
bool parentInferred = false;
|
||||
|
||||
std::vector<Entry> entries;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
Style* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
Style* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Array : public BaseValue<Array> {
|
||||
std::vector<std::unique_ptr<Item>> items;
|
||||
std::vector<std::unique_ptr<Item>> items;
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
Array* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
Array* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Plural : public BaseValue<Plural> {
|
||||
enum {
|
||||
Zero = 0,
|
||||
One,
|
||||
Two,
|
||||
Few,
|
||||
Many,
|
||||
Other,
|
||||
Count
|
||||
};
|
||||
enum { Zero = 0, One, Two, Few, Many, Other, Count };
|
||||
|
||||
std::array<std::unique_ptr<Item>, Count> values;
|
||||
std::array<std::unique_ptr<Item>, Count> values;
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
Plural* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
bool equals(const Value* value) const override;
|
||||
Plural* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Styleable : public BaseValue<Styleable> {
|
||||
std::vector<Reference> entries;
|
||||
std::vector<Reference> entries;
|
||||
|
||||
bool equals(const Value* value) const override;
|
||||
Styleable* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
void mergeWith(Styleable* styleable);
|
||||
bool equals(const Value* value) const override;
|
||||
Styleable* clone(StringPool* newPool) const override;
|
||||
void print(std::ostream* out) const override;
|
||||
void mergeWith(Styleable* styleable);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream operator for printing Value objects.
|
||||
*/
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
|
||||
value.print(&out);
|
||||
return out;
|
||||
value.print(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
|
||||
if (s.symbol.name) {
|
||||
out << s.symbol.name.value().entry;
|
||||
} else {
|
||||
out << "???";
|
||||
}
|
||||
return out << "=" << s.value;
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const Attribute::Symbol& s) {
|
||||
if (s.symbol.name) {
|
||||
out << s.symbol.name.value().entry;
|
||||
} else {
|
||||
out << "???";
|
||||
}
|
||||
return out << "=" << s.value;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_VALUES_H
|
||||
#endif // AAPT_RESOURCE_VALUES_H
|
||||
|
||||
@@ -20,96 +20,96 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceTypeTest, ParseResourceTypes) {
|
||||
const ResourceType* type = parseResourceType("anim");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnim);
|
||||
const ResourceType* type = parseResourceType("anim");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnim);
|
||||
|
||||
type = parseResourceType("animator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnimator);
|
||||
type = parseResourceType("animator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnimator);
|
||||
|
||||
type = parseResourceType("array");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kArray);
|
||||
type = parseResourceType("array");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kArray);
|
||||
|
||||
type = parseResourceType("attr");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttr);
|
||||
type = parseResourceType("attr");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttr);
|
||||
|
||||
type = parseResourceType("^attr-private");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttrPrivate);
|
||||
type = parseResourceType("^attr-private");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttrPrivate);
|
||||
|
||||
type = parseResourceType("bool");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kBool);
|
||||
type = parseResourceType("bool");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kBool);
|
||||
|
||||
type = parseResourceType("color");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kColor);
|
||||
type = parseResourceType("color");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kColor);
|
||||
|
||||
type = parseResourceType("dimen");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDimen);
|
||||
type = parseResourceType("dimen");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDimen);
|
||||
|
||||
type = parseResourceType("drawable");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDrawable);
|
||||
type = parseResourceType("drawable");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDrawable);
|
||||
|
||||
type = parseResourceType("fraction");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kFraction);
|
||||
type = parseResourceType("fraction");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kFraction);
|
||||
|
||||
type = parseResourceType("id");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kId);
|
||||
type = parseResourceType("id");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kId);
|
||||
|
||||
type = parseResourceType("integer");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInteger);
|
||||
type = parseResourceType("integer");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInteger);
|
||||
|
||||
type = parseResourceType("interpolator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInterpolator);
|
||||
type = parseResourceType("interpolator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInterpolator);
|
||||
|
||||
type = parseResourceType("layout");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kLayout);
|
||||
type = parseResourceType("layout");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kLayout);
|
||||
|
||||
type = parseResourceType("menu");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMenu);
|
||||
type = parseResourceType("menu");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMenu);
|
||||
|
||||
type = parseResourceType("mipmap");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMipmap);
|
||||
type = parseResourceType("mipmap");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMipmap);
|
||||
|
||||
type = parseResourceType("plurals");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kPlurals);
|
||||
type = parseResourceType("plurals");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kPlurals);
|
||||
|
||||
type = parseResourceType("raw");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kRaw);
|
||||
type = parseResourceType("raw");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kRaw);
|
||||
|
||||
type = parseResourceType("string");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kString);
|
||||
type = parseResourceType("string");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kString);
|
||||
|
||||
type = parseResourceType("style");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kStyle);
|
||||
type = parseResourceType("style");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kStyle);
|
||||
|
||||
type = parseResourceType("transition");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kTransition);
|
||||
type = parseResourceType("transition");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kTransition);
|
||||
|
||||
type = parseResourceType("xml");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kXml);
|
||||
type = parseResourceType("xml");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kXml);
|
||||
|
||||
type = parseResourceType("blahaha");
|
||||
EXPECT_EQ(type, nullptr);
|
||||
type = parseResourceType("blahaha");
|
||||
EXPECT_EQ(type, nullptr);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,33 +24,33 @@
|
||||
namespace aapt {
|
||||
|
||||
enum {
|
||||
SDK_CUPCAKE = 3,
|
||||
SDK_DONUT = 4,
|
||||
SDK_ECLAIR = 5,
|
||||
SDK_ECLAIR_0_1 = 6,
|
||||
SDK_ECLAIR_MR1 = 7,
|
||||
SDK_FROYO = 8,
|
||||
SDK_GINGERBREAD = 9,
|
||||
SDK_GINGERBREAD_MR1 = 10,
|
||||
SDK_HONEYCOMB = 11,
|
||||
SDK_HONEYCOMB_MR1 = 12,
|
||||
SDK_HONEYCOMB_MR2 = 13,
|
||||
SDK_ICE_CREAM_SANDWICH = 14,
|
||||
SDK_ICE_CREAM_SANDWICH_MR1 = 15,
|
||||
SDK_JELLY_BEAN = 16,
|
||||
SDK_JELLY_BEAN_MR1 = 17,
|
||||
SDK_JELLY_BEAN_MR2 = 18,
|
||||
SDK_KITKAT = 19,
|
||||
SDK_KITKAT_WATCH = 20,
|
||||
SDK_LOLLIPOP = 21,
|
||||
SDK_LOLLIPOP_MR1 = 22,
|
||||
SDK_MARSHMALLOW = 23,
|
||||
SDK_CUPCAKE = 3,
|
||||
SDK_DONUT = 4,
|
||||
SDK_ECLAIR = 5,
|
||||
SDK_ECLAIR_0_1 = 6,
|
||||
SDK_ECLAIR_MR1 = 7,
|
||||
SDK_FROYO = 8,
|
||||
SDK_GINGERBREAD = 9,
|
||||
SDK_GINGERBREAD_MR1 = 10,
|
||||
SDK_HONEYCOMB = 11,
|
||||
SDK_HONEYCOMB_MR1 = 12,
|
||||
SDK_HONEYCOMB_MR2 = 13,
|
||||
SDK_ICE_CREAM_SANDWICH = 14,
|
||||
SDK_ICE_CREAM_SANDWICH_MR1 = 15,
|
||||
SDK_JELLY_BEAN = 16,
|
||||
SDK_JELLY_BEAN_MR1 = 17,
|
||||
SDK_JELLY_BEAN_MR2 = 18,
|
||||
SDK_KITKAT = 19,
|
||||
SDK_KITKAT_WATCH = 20,
|
||||
SDK_LOLLIPOP = 21,
|
||||
SDK_LOLLIPOP_MR1 = 22,
|
||||
SDK_MARSHMALLOW = 23,
|
||||
};
|
||||
|
||||
size_t findAttributeSdkLevel(const ResourceId& id);
|
||||
size_t findAttributeSdkLevel(const ResourceName& name);
|
||||
std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion();
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SDK_CONSTANTS_H
|
||||
#endif // AAPT_SDK_CONSTANTS_H
|
||||
|
||||
@@ -21,18 +21,18 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
|
||||
EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
|
||||
EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
|
||||
}
|
||||
|
||||
TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
|
||||
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
|
||||
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -30,20 +30,19 @@ namespace aapt {
|
||||
* showing errors.
|
||||
*/
|
||||
struct Source {
|
||||
std::string path;
|
||||
Maybe<size_t> line;
|
||||
std::string path;
|
||||
Maybe<size_t> line;
|
||||
|
||||
Source() = default;
|
||||
Source() = default;
|
||||
|
||||
inline Source(const StringPiece& path) : path(path.toString()) { // NOLINT(implicit)
|
||||
}
|
||||
inline Source(const StringPiece& path)
|
||||
: path(path.toString()) { // NOLINT(implicit)
|
||||
}
|
||||
|
||||
inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) {
|
||||
}
|
||||
inline Source(const StringPiece& path, size_t line)
|
||||
: path(path.toString()), line(line) {}
|
||||
|
||||
inline Source withLine(size_t line) const {
|
||||
return Source(path, line);
|
||||
}
|
||||
inline Source withLine(size_t line) const { return Source(path, line); }
|
||||
};
|
||||
|
||||
//
|
||||
@@ -51,30 +50,30 @@ struct Source {
|
||||
//
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
|
||||
out << source.path;
|
||||
if (source.line) {
|
||||
out << ":" << source.line.value();
|
||||
}
|
||||
return out;
|
||||
out << source.path;
|
||||
if (source.line) {
|
||||
out << ":" << source.line.value();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline bool operator==(const Source& lhs, const Source& rhs) {
|
||||
return lhs.path == rhs.path && lhs.line == rhs.line;
|
||||
return lhs.path == rhs.path && lhs.line == rhs.line;
|
||||
}
|
||||
|
||||
inline bool operator<(const Source& lhs, const Source& rhs) {
|
||||
int cmp = lhs.path.compare(rhs.path);
|
||||
if (cmp < 0) return true;
|
||||
if (cmp > 0) return false;
|
||||
if (lhs.line) {
|
||||
if (rhs.line) {
|
||||
return lhs.line.value() < rhs.line.value();
|
||||
}
|
||||
return false;
|
||||
int cmp = lhs.path.compare(rhs.path);
|
||||
if (cmp < 0) return true;
|
||||
if (cmp > 0) return false;
|
||||
if (lhs.line) {
|
||||
if (rhs.line) {
|
||||
return lhs.line.value() < rhs.line.value();
|
||||
}
|
||||
return bool(rhs.line);
|
||||
return false;
|
||||
}
|
||||
return bool(rhs.line);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SOURCE_H
|
||||
#endif // AAPT_SOURCE_H
|
||||
|
||||
@@ -19,395 +19,400 @@
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
StringPool::Ref::Ref() : mEntry(nullptr) {
|
||||
}
|
||||
StringPool::Ref::Ref() : mEntry(nullptr) {}
|
||||
|
||||
StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::~Ref() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string* StringPool::Ref::operator->() const {
|
||||
return &mEntry->value;
|
||||
return &mEntry->value;
|
||||
}
|
||||
|
||||
const std::string& StringPool::Ref::operator*() const {
|
||||
return mEntry->value;
|
||||
}
|
||||
const std::string& StringPool::Ref::operator*() const { return mEntry->value; }
|
||||
|
||||
size_t StringPool::Ref::getIndex() const {
|
||||
return mEntry->index;
|
||||
}
|
||||
size_t StringPool::Ref::getIndex() const { return mEntry->index; }
|
||||
|
||||
const StringPool::Context& StringPool::Ref::getContext() const {
|
||||
return mEntry->context;
|
||||
return mEntry->context;
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
|
||||
}
|
||||
StringPool::StyleRef::StyleRef() : mEntry(nullptr) {}
|
||||
|
||||
StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
|
||||
: mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::~StyleRef() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
StringPool::StyleRef& StringPool::StyleRef::operator=(
|
||||
const StringPool::StyleRef& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
|
||||
return mEntry;
|
||||
return mEntry;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
|
||||
return *mEntry;
|
||||
return *mEntry;
|
||||
}
|
||||
|
||||
size_t StringPool::StyleRef::getIndex() const {
|
||||
return mEntry->str.getIndex();
|
||||
}
|
||||
size_t StringPool::StyleRef::getIndex() const { return mEntry->str.getIndex(); }
|
||||
|
||||
const StringPool::Context& StringPool::StyleRef::getContext() const {
|
||||
return mEntry->str.getContext();
|
||||
return mEntry->str.getContext();
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece& str) {
|
||||
return makeRefImpl(str, Context{}, true);
|
||||
return makeRefImpl(str, Context{}, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece& str, const Context& context) {
|
||||
return makeRefImpl(str, context, true);
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece& str,
|
||||
const Context& context) {
|
||||
return makeRefImpl(str, context, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRefImpl(const StringPiece& str, const Context& context,
|
||||
bool unique) {
|
||||
if (unique) {
|
||||
auto iter = mIndexedStrings.find(str);
|
||||
if (iter != std::end(mIndexedStrings)) {
|
||||
return Ref(iter->second);
|
||||
}
|
||||
StringPool::Ref StringPool::makeRefImpl(const StringPiece& str,
|
||||
const Context& context, bool unique) {
|
||||
if (unique) {
|
||||
auto iter = mIndexedStrings.find(str);
|
||||
if (iter != std::end(mIndexedStrings)) {
|
||||
return Ref(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.toString();
|
||||
entry->context = context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
return Ref(entry);
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.toString();
|
||||
entry->context = context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
return Ref(entry);
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
|
||||
return makeRef(str, Context{});
|
||||
return makeRef(str, Context{});
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) {
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.str;
|
||||
entry->context = context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str,
|
||||
const Context& context) {
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.str;
|
||||
entry->context = context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
|
||||
StyleEntry* styleEntry = new StyleEntry();
|
||||
styleEntry->str = Ref(entry);
|
||||
for (const aapt::Span& span : str.spans) {
|
||||
styleEntry->spans.emplace_back(Span{ makeRef(span.name), span.firstChar, span.lastChar });
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
StyleEntry* styleEntry = new StyleEntry();
|
||||
styleEntry->str = Ref(entry);
|
||||
for (const aapt::Span& span : str.spans) {
|
||||
styleEntry->spans.emplace_back(
|
||||
Span{makeRef(span.name), span.firstChar, span.lastChar});
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
|
||||
Entry* entry = new Entry();
|
||||
entry->value = *ref.mEntry->str;
|
||||
entry->context = ref.mEntry->str.mEntry->context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
Entry* entry = new Entry();
|
||||
entry->value = *ref.mEntry->str;
|
||||
entry->context = ref.mEntry->str.mEntry->context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
|
||||
StyleEntry* styleEntry = new StyleEntry();
|
||||
styleEntry->str = Ref(entry);
|
||||
for (const Span& span : ref.mEntry->spans) {
|
||||
styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar });
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
StyleEntry* styleEntry = new StyleEntry();
|
||||
styleEntry->str = Ref(entry);
|
||||
for (const Span& span : ref.mEntry->spans) {
|
||||
styleEntry->spans.emplace_back(
|
||||
Span{makeRef(*span.name), span.firstChar, span.lastChar});
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
}
|
||||
|
||||
void StringPool::merge(StringPool&& pool) {
|
||||
mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
|
||||
pool.mIndexedStrings.clear();
|
||||
std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings));
|
||||
pool.mStrings.clear();
|
||||
std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles));
|
||||
pool.mStyles.clear();
|
||||
mIndexedStrings.insert(pool.mIndexedStrings.begin(),
|
||||
pool.mIndexedStrings.end());
|
||||
pool.mIndexedStrings.clear();
|
||||
std::move(pool.mStrings.begin(), pool.mStrings.end(),
|
||||
std::back_inserter(mStrings));
|
||||
pool.mStrings.clear();
|
||||
std::move(pool.mStyles.begin(), pool.mStyles.end(),
|
||||
std::back_inserter(mStyles));
|
||||
pool.mStyles.clear();
|
||||
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
|
||||
mStrings.reserve(mStrings.size() + stringCount);
|
||||
mStyles.reserve(mStyles.size() + styleCount);
|
||||
mStrings.reserve(mStrings.size() + stringCount);
|
||||
mStyles.reserve(mStyles.size() + styleCount);
|
||||
}
|
||||
|
||||
void StringPool::prune() {
|
||||
const auto iterEnd = std::end(mIndexedStrings);
|
||||
auto indexIter = std::begin(mIndexedStrings);
|
||||
while (indexIter != iterEnd) {
|
||||
if (indexIter->second->ref <= 0) {
|
||||
indexIter = mIndexedStrings.erase(indexIter);
|
||||
} else {
|
||||
++indexIter;
|
||||
}
|
||||
const auto iterEnd = std::end(mIndexedStrings);
|
||||
auto indexIter = std::begin(mIndexedStrings);
|
||||
while (indexIter != iterEnd) {
|
||||
if (indexIter->second->ref <= 0) {
|
||||
indexIter = mIndexedStrings.erase(indexIter);
|
||||
} else {
|
||||
++indexIter;
|
||||
}
|
||||
}
|
||||
|
||||
auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings),
|
||||
[](const std::unique_ptr<Entry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
}
|
||||
);
|
||||
auto endIter2 =
|
||||
std::remove_if(std::begin(mStrings), std::end(mStrings),
|
||||
[](const std::unique_ptr<Entry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
});
|
||||
|
||||
auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles),
|
||||
[](const std::unique_ptr<StyleEntry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
}
|
||||
);
|
||||
auto endIter3 =
|
||||
std::remove_if(std::begin(mStyles), std::end(mStyles),
|
||||
[](const std::unique_ptr<StyleEntry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
});
|
||||
|
||||
// Remove the entries at the end or else we'll be accessing
|
||||
// a deleted string from the StyleEntry.
|
||||
mStrings.erase(endIter2, std::end(mStrings));
|
||||
mStyles.erase(endIter3, std::end(mStyles));
|
||||
// Remove the entries at the end or else we'll be accessing
|
||||
// a deleted string from the StyleEntry.
|
||||
mStrings.erase(endIter2, std::end(mStrings));
|
||||
mStyles.erase(endIter3, std::end(mStyles));
|
||||
|
||||
// Reassign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
// Reassign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
|
||||
std::sort(std::begin(mStrings), std::end(mStrings),
|
||||
[&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool {
|
||||
return cmp(*a, *b);
|
||||
}
|
||||
);
|
||||
void StringPool::sort(
|
||||
const std::function<bool(const Entry&, const Entry&)>& cmp) {
|
||||
std::sort(
|
||||
std::begin(mStrings), std::end(mStrings),
|
||||
[&cmp](const std::unique_ptr<Entry>& a,
|
||||
const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); });
|
||||
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
|
||||
// Reorder the styles.
|
||||
std::sort(std::begin(mStyles), std::end(mStyles),
|
||||
// Reorder the styles.
|
||||
std::sort(std::begin(mStyles), std::end(mStyles),
|
||||
[](const std::unique_ptr<StyleEntry>& lhs,
|
||||
const std::unique_ptr<StyleEntry>& rhs) -> bool {
|
||||
return lhs->str.getIndex() < rhs->str.getIndex();
|
||||
}
|
||||
);
|
||||
return lhs->str.getIndex() < rhs->str.getIndex();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* encodeLength(T* data, size_t length) {
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
constexpr size_t kMaxSize = kMask - 1;
|
||||
if (length > kMaxSize) {
|
||||
*data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
|
||||
}
|
||||
*data++ = length;
|
||||
return data;
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
constexpr size_t kMaxSize = kMask - 1;
|
||||
if (length > kMaxSize) {
|
||||
*data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
|
||||
}
|
||||
*data++ = length;
|
||||
return data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static size_t encodedLengthUnits(size_t length) {
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
constexpr size_t kMaxSize = kMask - 1;
|
||||
return length > kMaxSize ? 2 : 1;
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
constexpr size_t kMaxSize = kMask - 1;
|
||||
return length > kMaxSize ? 2 : 1;
|
||||
}
|
||||
|
||||
|
||||
bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
|
||||
const size_t startIndex = out->size();
|
||||
android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>();
|
||||
header->header.type = android::RES_STRING_POOL_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->stringCount = pool.size();
|
||||
const size_t startIndex = out->size();
|
||||
android::ResStringPool_header* header =
|
||||
out->nextBlock<android::ResStringPool_header>();
|
||||
header->header.type = android::RES_STRING_POOL_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->stringCount = pool.size();
|
||||
if (utf8) {
|
||||
header->flags |= android::ResStringPool_header::UTF8_FLAG;
|
||||
}
|
||||
|
||||
uint32_t* indices =
|
||||
pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
|
||||
|
||||
uint32_t* styleIndices = nullptr;
|
||||
if (!pool.mStyles.empty()) {
|
||||
header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
|
||||
styleIndices = out->nextBlock<uint32_t>(header->styleCount);
|
||||
}
|
||||
|
||||
const size_t beforeStringsIndex = out->size();
|
||||
header->stringsStart = beforeStringsIndex - startIndex;
|
||||
|
||||
for (const auto& entry : pool) {
|
||||
*indices = out->size() - beforeStringsIndex;
|
||||
indices++;
|
||||
|
||||
if (utf8) {
|
||||
header->flags |= android::ResStringPool_header::UTF8_FLAG;
|
||||
}
|
||||
|
||||
uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
|
||||
|
||||
uint32_t* styleIndices = nullptr;
|
||||
if (!pool.mStyles.empty()) {
|
||||
header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
|
||||
styleIndices = out->nextBlock<uint32_t>(header->styleCount);
|
||||
}
|
||||
|
||||
const size_t beforeStringsIndex = out->size();
|
||||
header->stringsStart = beforeStringsIndex - startIndex;
|
||||
|
||||
for (const auto& entry : pool) {
|
||||
*indices = out->size() - beforeStringsIndex;
|
||||
indices++;
|
||||
|
||||
if (utf8) {
|
||||
const std::string& encoded = entry->value;
|
||||
const ssize_t utf16Length = utf8_to_utf16_length(
|
||||
reinterpret_cast<const uint8_t*>(entry->value.data()), entry->value.size());
|
||||
assert(utf16Length >= 0);
|
||||
|
||||
const size_t totalSize = encodedLengthUnits<char>(utf16Length)
|
||||
+ encodedLengthUnits<char>(encoded.length())
|
||||
+ encoded.size() + 1;
|
||||
|
||||
char* data = out->nextBlock<char>(totalSize);
|
||||
|
||||
// First encode the UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
|
||||
// Now encode the size of the real UTF8 string.
|
||||
data = encodeLength(data, encoded.length());
|
||||
strncpy(data, encoded.data(), encoded.size());
|
||||
|
||||
} else {
|
||||
const std::u16string encoded = util::utf8ToUtf16(entry->value);
|
||||
const ssize_t utf16Length = encoded.size();
|
||||
|
||||
// Total number of 16-bit words to write.
|
||||
const size_t totalSize = encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1;
|
||||
|
||||
char16_t* data = out->nextBlock<char16_t>(totalSize);
|
||||
|
||||
// Encode the actual UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
const size_t byteLength = encoded.size() * sizeof(char16_t);
|
||||
|
||||
// NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size())
|
||||
// truncates the string.
|
||||
memcpy(data, encoded.data(), byteLength);
|
||||
|
||||
// The null-terminating character is already here due to the block of data being set
|
||||
// to 0s on allocation.
|
||||
}
|
||||
const std::string& encoded = entry->value;
|
||||
const ssize_t utf16Length = utf8_to_utf16_length(
|
||||
reinterpret_cast<const uint8_t*>(entry->value.data()),
|
||||
entry->value.size());
|
||||
assert(utf16Length >= 0);
|
||||
|
||||
const size_t totalSize = encodedLengthUnits<char>(utf16Length) +
|
||||
encodedLengthUnits<char>(encoded.length()) +
|
||||
encoded.size() + 1;
|
||||
|
||||
char* data = out->nextBlock<char>(totalSize);
|
||||
|
||||
// First encode the UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
|
||||
// Now encode the size of the real UTF8 string.
|
||||
data = encodeLength(data, encoded.length());
|
||||
strncpy(data, encoded.data(), encoded.size());
|
||||
|
||||
} else {
|
||||
const std::u16string encoded = util::utf8ToUtf16(entry->value);
|
||||
const ssize_t utf16Length = encoded.size();
|
||||
|
||||
// Total number of 16-bit words to write.
|
||||
const size_t totalSize =
|
||||
encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1;
|
||||
|
||||
char16_t* data = out->nextBlock<char16_t>(totalSize);
|
||||
|
||||
// Encode the actual UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
const size_t byteLength = encoded.size() * sizeof(char16_t);
|
||||
|
||||
// NOTE: For some reason, strncpy16(data, entry->value.data(),
|
||||
// entry->value.size())
|
||||
// truncates the string.
|
||||
memcpy(data, encoded.data(), byteLength);
|
||||
|
||||
// The null-terminating character is already here due to the block of data
|
||||
// being set
|
||||
// to 0s on allocation.
|
||||
}
|
||||
}
|
||||
|
||||
out->align4();
|
||||
|
||||
if (!pool.mStyles.empty()) {
|
||||
const size_t beforeStylesIndex = out->size();
|
||||
header->stylesStart = beforeStylesIndex - startIndex;
|
||||
|
||||
size_t currentIndex = 0;
|
||||
for (const auto& entry : pool.mStyles) {
|
||||
while (entry->str.getIndex() > currentIndex) {
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
uint32_t* spanOffset = out->nextBlock<uint32_t>();
|
||||
*spanOffset = android::ResStringPool_span::END;
|
||||
}
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
android::ResStringPool_span* span =
|
||||
out->nextBlock<android::ResStringPool_span>(entry->spans.size());
|
||||
for (const auto& s : entry->spans) {
|
||||
span->name.index = s.name.getIndex();
|
||||
span->firstChar = s.firstChar;
|
||||
span->lastChar = s.lastChar;
|
||||
span++;
|
||||
}
|
||||
|
||||
uint32_t* spanEnd = out->nextBlock<uint32_t>();
|
||||
*spanEnd = android::ResStringPool_span::END;
|
||||
}
|
||||
|
||||
// The error checking code in the platform looks for an entire
|
||||
// ResStringPool_span structure worth of 0xFFFFFFFF at the end
|
||||
// of the style block, so fill in the remaining 2 32bit words
|
||||
// with 0xFFFFFFFF.
|
||||
const size_t paddingLength = sizeof(android::ResStringPool_span) -
|
||||
sizeof(android::ResStringPool_span::name);
|
||||
uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
|
||||
memset(padding, 0xff, paddingLength);
|
||||
out->align4();
|
||||
|
||||
if (!pool.mStyles.empty()) {
|
||||
const size_t beforeStylesIndex = out->size();
|
||||
header->stylesStart = beforeStylesIndex - startIndex;
|
||||
|
||||
size_t currentIndex = 0;
|
||||
for (const auto& entry : pool.mStyles) {
|
||||
while (entry->str.getIndex() > currentIndex) {
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
uint32_t* spanOffset = out->nextBlock<uint32_t>();
|
||||
*spanOffset = android::ResStringPool_span::END;
|
||||
}
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
android::ResStringPool_span* span =
|
||||
out->nextBlock<android::ResStringPool_span>(entry->spans.size());
|
||||
for (const auto& s : entry->spans) {
|
||||
span->name.index = s.name.getIndex();
|
||||
span->firstChar = s.firstChar;
|
||||
span->lastChar = s.lastChar;
|
||||
span++;
|
||||
}
|
||||
|
||||
uint32_t* spanEnd = out->nextBlock<uint32_t>();
|
||||
*spanEnd = android::ResStringPool_span::END;
|
||||
}
|
||||
|
||||
// The error checking code in the platform looks for an entire
|
||||
// ResStringPool_span structure worth of 0xFFFFFFFF at the end
|
||||
// of the style block, so fill in the remaining 2 32bit words
|
||||
// with 0xFFFFFFFF.
|
||||
const size_t paddingLength = sizeof(android::ResStringPool_span)
|
||||
- sizeof(android::ResStringPool_span::name);
|
||||
uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
|
||||
memset(padding, 0xff, paddingLength);
|
||||
out->align4();
|
||||
}
|
||||
header->header.size = out->size() - startIndex;
|
||||
return true;
|
||||
}
|
||||
header->header.size = out->size() - startIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
|
||||
return flatten(out, pool, true);
|
||||
return flatten(out, pool, true);
|
||||
}
|
||||
|
||||
bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) {
|
||||
return flatten(out, pool, false);
|
||||
return flatten(out, pool, false);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,207 +17,205 @@
|
||||
#ifndef AAPT_STRING_POOL_H
|
||||
#define AAPT_STRING_POOL_H
|
||||
|
||||
#include "util/BigBuffer.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Span {
|
||||
std::string name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
std::string name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
};
|
||||
|
||||
struct StyleString {
|
||||
std::string str;
|
||||
std::vector<Span> spans;
|
||||
std::string str;
|
||||
std::vector<Span> spans;
|
||||
};
|
||||
|
||||
class StringPool {
|
||||
public:
|
||||
struct Context {
|
||||
uint32_t priority;
|
||||
ConfigDescription config;
|
||||
};
|
||||
public:
|
||||
struct Context {
|
||||
uint32_t priority;
|
||||
ConfigDescription config;
|
||||
};
|
||||
|
||||
class Entry;
|
||||
class Entry;
|
||||
|
||||
class Ref {
|
||||
public:
|
||||
Ref();
|
||||
Ref(const Ref&);
|
||||
~Ref();
|
||||
class Ref {
|
||||
public:
|
||||
Ref();
|
||||
Ref(const Ref&);
|
||||
~Ref();
|
||||
|
||||
Ref& operator=(const Ref& rhs);
|
||||
const std::string* operator->() const;
|
||||
const std::string& operator*() const;
|
||||
Ref& operator=(const Ref& rhs);
|
||||
const std::string* operator->() const;
|
||||
const std::string& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
explicit Ref(Entry* entry);
|
||||
explicit Ref(Entry* entry);
|
||||
|
||||
Entry* mEntry;
|
||||
};
|
||||
Entry* mEntry;
|
||||
};
|
||||
|
||||
class StyleEntry;
|
||||
class StyleEntry;
|
||||
|
||||
class StyleRef {
|
||||
public:
|
||||
StyleRef();
|
||||
StyleRef(const StyleRef&);
|
||||
~StyleRef();
|
||||
class StyleRef {
|
||||
public:
|
||||
StyleRef();
|
||||
StyleRef(const StyleRef&);
|
||||
~StyleRef();
|
||||
|
||||
StyleRef& operator=(const StyleRef& rhs);
|
||||
const StyleEntry* operator->() const;
|
||||
const StyleEntry& operator*() const;
|
||||
StyleRef& operator=(const StyleRef& rhs);
|
||||
const StyleEntry* operator->() const;
|
||||
const StyleEntry& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
explicit StyleRef(StyleEntry* entry);
|
||||
explicit StyleRef(StyleEntry* entry);
|
||||
|
||||
StyleEntry* mEntry;
|
||||
};
|
||||
StyleEntry* mEntry;
|
||||
};
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
std::string value;
|
||||
Context context;
|
||||
size_t index;
|
||||
class Entry {
|
||||
public:
|
||||
std::string value;
|
||||
Context context;
|
||||
size_t index;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class Ref;
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class Ref;
|
||||
|
||||
int ref;
|
||||
};
|
||||
int ref;
|
||||
};
|
||||
|
||||
struct Span {
|
||||
Ref name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
};
|
||||
struct Span {
|
||||
Ref name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
};
|
||||
|
||||
class StyleEntry {
|
||||
public:
|
||||
Ref str;
|
||||
std::vector<Span> spans;
|
||||
class StyleEntry {
|
||||
public:
|
||||
Ref str;
|
||||
std::vector<Span> spans;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class StyleRef;
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class StyleRef;
|
||||
|
||||
int ref;
|
||||
};
|
||||
int ref;
|
||||
};
|
||||
|
||||
using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
|
||||
using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
|
||||
|
||||
static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
|
||||
static bool flattenUtf16(BigBuffer* out, const StringPool& pool);
|
||||
static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
|
||||
static bool flattenUtf16(BigBuffer* out, const StringPool& pool);
|
||||
|
||||
StringPool() = default;
|
||||
StringPool(const StringPool&) = delete;
|
||||
StringPool() = default;
|
||||
StringPool(const StringPool&) = delete;
|
||||
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece& str);
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists, with a context
|
||||
* object that can be used when sorting the string pool. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece& str, const Context& context);
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists, with a context
|
||||
* object that can be used when sorting the string pool. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece& str, const Context& context);
|
||||
|
||||
/**
|
||||
* Adds a style to the string pool and returns a reference to it.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str);
|
||||
/**
|
||||
* Adds a style to the string pool and returns a reference to it.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str);
|
||||
|
||||
/**
|
||||
* Adds a style to the string pool with a context object that
|
||||
* can be used when sorting the string pool. Returns a reference
|
||||
* to the style in the string pool.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str, const Context& context);
|
||||
/**
|
||||
* Adds a style to the string pool with a context object that
|
||||
* can be used when sorting the string pool. Returns a reference
|
||||
* to the style in the string pool.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str, const Context& context);
|
||||
|
||||
/**
|
||||
* Adds a style from another string pool. Returns a reference to the
|
||||
* style in the string pool.
|
||||
*/
|
||||
StyleRef makeRef(const StyleRef& ref);
|
||||
/**
|
||||
* Adds a style from another string pool. Returns a reference to the
|
||||
* style in the string pool.
|
||||
*/
|
||||
StyleRef makeRef(const StyleRef& ref);
|
||||
|
||||
/**
|
||||
* Moves pool into this one without coalescing strings. When this
|
||||
* function returns, pool will be empty.
|
||||
*/
|
||||
void merge(StringPool&& pool);
|
||||
/**
|
||||
* Moves pool into this one without coalescing strings. When this
|
||||
* function returns, pool will be empty.
|
||||
*/
|
||||
void merge(StringPool&& pool);
|
||||
|
||||
/**
|
||||
* Retuns the number of strings in the table.
|
||||
*/
|
||||
inline size_t size() const;
|
||||
/**
|
||||
* Retuns the number of strings in the table.
|
||||
*/
|
||||
inline size_t size() const;
|
||||
|
||||
/**
|
||||
* Reserves space for strings and styles as an optimization.
|
||||
*/
|
||||
void hintWillAdd(size_t stringCount, size_t styleCount);
|
||||
/**
|
||||
* Reserves space for strings and styles as an optimization.
|
||||
*/
|
||||
void hintWillAdd(size_t stringCount, size_t styleCount);
|
||||
|
||||
/**
|
||||
* Sorts the strings according to some comparison function.
|
||||
*/
|
||||
void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
|
||||
/**
|
||||
* Sorts the strings according to some comparison function.
|
||||
*/
|
||||
void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
|
||||
|
||||
/**
|
||||
* Removes any strings that have no references.
|
||||
*/
|
||||
void prune();
|
||||
/**
|
||||
* Removes any strings that have no references.
|
||||
*/
|
||||
void prune();
|
||||
|
||||
private:
|
||||
friend const_iterator begin(const StringPool& pool);
|
||||
friend const_iterator end(const StringPool& pool);
|
||||
private:
|
||||
friend const_iterator begin(const StringPool& pool);
|
||||
friend const_iterator end(const StringPool& pool);
|
||||
|
||||
static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8);
|
||||
static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8);
|
||||
|
||||
Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique);
|
||||
Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique);
|
||||
|
||||
std::vector<std::unique_ptr<Entry>> mStrings;
|
||||
std::vector<std::unique_ptr<StyleEntry>> mStyles;
|
||||
std::unordered_multimap<StringPiece, Entry*> mIndexedStrings;
|
||||
std::vector<std::unique_ptr<Entry>> mStrings;
|
||||
std::vector<std::unique_ptr<StyleEntry>> mStyles;
|
||||
std::unordered_multimap<StringPiece, Entry*> mIndexedStrings;
|
||||
};
|
||||
|
||||
//
|
||||
// Inline implementation
|
||||
//
|
||||
|
||||
inline size_t StringPool::size() const {
|
||||
return mStrings.size();
|
||||
}
|
||||
inline size_t StringPool::size() const { return mStrings.size(); }
|
||||
|
||||
inline StringPool::const_iterator begin(const StringPool& pool) {
|
||||
return pool.mStrings.begin();
|
||||
return pool.mStrings.begin();
|
||||
}
|
||||
|
||||
inline StringPool::const_iterator end(const StringPool& pool) {
|
||||
return pool.mStrings.end();
|
||||
return pool.mStrings.end();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_STRING_POOL_H
|
||||
#endif // AAPT_STRING_POOL_H
|
||||
|
||||
@@ -23,246 +23,245 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(StringPoolTest, InsertOneString) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, InsertTwoUniqueStrings) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref2 = pool.makeRef("hey");
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref2 = pool.makeRef("hey");
|
||||
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(*ref2, "hey");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(*ref2, "hey");
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref2 = pool.makeRef("wut");
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref2 = pool.makeRef("wut");
|
||||
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(*ref2, "wut");
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(*ref2, "wut");
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::Ref ref2 = pool.makeRef("a");
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::Ref ref2 = pool.makeRef("a");
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, PruneStringsWithNoReferences) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref refA = pool.makeRef("foo");
|
||||
{
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
}
|
||||
StringPool::Ref refB = pool.makeRef("bar");
|
||||
|
||||
EXPECT_EQ(3u, pool.size());
|
||||
pool.prune();
|
||||
StringPool::Ref refA = pool.makeRef("foo");
|
||||
{
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
StringPool::const_iterator iter = begin(pool);
|
||||
EXPECT_EQ((*iter)->value, "foo");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
++iter;
|
||||
EXPECT_EQ((*iter)->value, "bar");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
}
|
||||
StringPool::Ref refB = pool.makeRef("bar");
|
||||
|
||||
EXPECT_EQ(3u, pool.size());
|
||||
pool.prune();
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
StringPool::const_iterator iter = begin(pool);
|
||||
EXPECT_EQ((*iter)->value, "foo");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
++iter;
|
||||
EXPECT_EQ((*iter)->value, "bar");
|
||||
EXPECT_LT((*iter)->index, 2u);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {"a"} });
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::StyleRef ref2 = pool.makeRef(StyleString{{"a"}});
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
|
||||
EXPECT_EQ(*ref, "z");
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(*ref, "z");
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
|
||||
EXPECT_EQ(*ref, "z");
|
||||
EXPECT_EQ(2u, ref.getIndex());
|
||||
|
||||
EXPECT_EQ(*ref, "z");
|
||||
EXPECT_EQ(2u, ref.getIndex());
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(0u, ref2.getIndex());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(0u, ref2.getIndex());
|
||||
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(1u, ref3.getIndex());
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(1u, ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndStillDedupe) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::Ref ref2 = pool.makeRef("a");
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
StringPool::Ref ref = pool.makeRef("z");
|
||||
StringPool::Ref ref2 = pool.makeRef("a");
|
||||
StringPool::Ref ref3 = pool.makeRef("m");
|
||||
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
|
||||
StringPool::Ref ref4 = pool.makeRef("z");
|
||||
StringPool::Ref ref5 = pool.makeRef("a");
|
||||
StringPool::Ref ref6 = pool.makeRef("m");
|
||||
StringPool::Ref ref4 = pool.makeRef("z");
|
||||
StringPool::Ref ref5 = pool.makeRef("a");
|
||||
StringPool::Ref ref6 = pool.makeRef("m");
|
||||
|
||||
EXPECT_EQ(ref4.getIndex(), ref.getIndex());
|
||||
EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
|
||||
EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
|
||||
EXPECT_EQ(ref4.getIndex(), ref.getIndex());
|
||||
EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
|
||||
EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, AddStyles) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StyleString str {
|
||||
{ "android" },
|
||||
{
|
||||
Span{ { "b" }, 2, 6 }
|
||||
}
|
||||
};
|
||||
StyleString str{{"android"}, {Span{{"b"}, 2, 6}}};
|
||||
|
||||
StringPool::StyleRef ref = pool.makeRef(str);
|
||||
StringPool::StyleRef ref = pool.makeRef(str);
|
||||
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(std::string("android"), *(ref->str));
|
||||
ASSERT_EQ(1u, ref->spans.size());
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(std::string("android"), *(ref->str));
|
||||
ASSERT_EQ(1u, ref->spans.size());
|
||||
|
||||
const StringPool::Span& span = ref->spans.front();
|
||||
EXPECT_EQ(*(span.name), "b");
|
||||
EXPECT_EQ(2u, span.firstChar);
|
||||
EXPECT_EQ(6u, span.lastChar);
|
||||
const StringPool::Span& span = ref->spans.front();
|
||||
EXPECT_EQ(*(span.name), "b");
|
||||
EXPECT_EQ(2u, span.firstChar);
|
||||
EXPECT_EQ(6u, span.lastChar);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("android");
|
||||
StringPool::Ref ref = pool.makeRef("android");
|
||||
|
||||
StyleString str { { "android" } };
|
||||
StringPool::StyleRef styleRef = pool.makeRef(str);
|
||||
StyleString str{{"android"}};
|
||||
StringPool::StyleRef styleRef = pool.makeRef(str);
|
||||
|
||||
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
|
||||
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
|
||||
StringPool pool;
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf8(&buffer, pool);
|
||||
StringPool pool;
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf8(&buffer, pool);
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, FlattenOddCharactersUtf16) {
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
|
||||
StringPool pool;
|
||||
pool.makeRef("\u093f");
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf16(&buffer, pool);
|
||||
StringPool pool;
|
||||
pool.makeRef("\u093f");
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf16(&buffer, pool);
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
size_t len = 0;
|
||||
const char16_t* str = test.stringAt(0, &len);
|
||||
EXPECT_EQ(1u, len);
|
||||
EXPECT_EQ(u'\u093f', *str);
|
||||
EXPECT_EQ(0u, str[1]);
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
size_t len = 0;
|
||||
const char16_t* str = test.stringAt(0, &len);
|
||||
EXPECT_EQ(1u, len);
|
||||
EXPECT_EQ(u'\u093f', *str);
|
||||
EXPECT_EQ(0u, str[1]);
|
||||
}
|
||||
|
||||
constexpr const char* sLongString = "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
|
||||
constexpr const char* sLongString =
|
||||
"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑"
|
||||
"え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限"
|
||||
"します。メール、SMSや、同期を使 "
|
||||
"用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ"
|
||||
"ーバーは端末の充電中は自動的にOFFになります。";
|
||||
|
||||
TEST(StringPoolTest, Flatten) {
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
|
||||
StringPool pool;
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref1 = pool.makeRef("hello");
|
||||
StringPool::Ref ref2 = pool.makeRef("goodbye");
|
||||
StringPool::Ref ref3 = pool.makeRef(sLongString);
|
||||
StringPool::Ref ref4 = pool.makeRef("");
|
||||
StringPool::StyleRef ref5 = pool.makeRef(StyleString{
|
||||
{ "style" },
|
||||
{ Span{ { "b" }, 0, 1 }, Span{ { "i" }, 2, 3 } }
|
||||
});
|
||||
StringPool::Ref ref1 = pool.makeRef("hello");
|
||||
StringPool::Ref ref2 = pool.makeRef("goodbye");
|
||||
StringPool::Ref ref3 = pool.makeRef(sLongString);
|
||||
StringPool::Ref ref4 = pool.makeRef("");
|
||||
StringPool::StyleRef ref5 = pool.makeRef(
|
||||
StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
|
||||
|
||||
EXPECT_EQ(0u, ref1.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(3u, ref4.getIndex());
|
||||
EXPECT_EQ(4u, ref5.getIndex());
|
||||
EXPECT_EQ(0u, ref1.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(3u, ref4.getIndex());
|
||||
EXPECT_EQ(4u, ref5.getIndex());
|
||||
|
||||
BigBuffer buffers[2] = { BigBuffer(1024), BigBuffer(1024) };
|
||||
StringPool::flattenUtf8(&buffers[0], pool);
|
||||
StringPool::flattenUtf16(&buffers[1], pool);
|
||||
BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
|
||||
StringPool::flattenUtf8(&buffers[0], pool);
|
||||
StringPool::flattenUtf16(&buffers[1], pool);
|
||||
|
||||
// Test both UTF-8 and UTF-16 buffers.
|
||||
for (const BigBuffer& buffer : buffers) {
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
// Test both UTF-8 and UTF-16 buffers.
|
||||
for (const BigBuffer& buffer : buffers) {
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
|
||||
EXPECT_EQ(std::string("hello"), util::getString(test, 0));
|
||||
EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0));
|
||||
EXPECT_EQ(std::string("hello"), util::getString(test, 0));
|
||||
EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0));
|
||||
|
||||
EXPECT_EQ(std::string("goodbye"), util::getString(test, 1));
|
||||
EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1));
|
||||
EXPECT_EQ(std::string("goodbye"), util::getString(test, 1));
|
||||
EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1));
|
||||
|
||||
EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2));
|
||||
EXPECT_EQ(util::utf8ToUtf16(sLongString), util::getString16(test, 2).toString());
|
||||
EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2));
|
||||
EXPECT_EQ(util::utf8ToUtf16(sLongString),
|
||||
util::getString16(test, 2).toString());
|
||||
|
||||
size_t len;
|
||||
EXPECT_TRUE(test.stringAt(3, &len) != nullptr || test.string8At(3, &len) != nullptr);
|
||||
size_t len;
|
||||
EXPECT_TRUE(test.stringAt(3, &len) != nullptr ||
|
||||
test.string8At(3, &len) != nullptr);
|
||||
|
||||
EXPECT_EQ(std::string("style"), util::getString(test, 4));
|
||||
EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4));
|
||||
EXPECT_EQ(std::string("style"), util::getString(test, 4));
|
||||
EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4));
|
||||
|
||||
const ResStringPool_span* span = test.styleAt(4);
|
||||
ASSERT_NE(nullptr, span);
|
||||
EXPECT_EQ(std::string("b"), util::getString(test, span->name.index));
|
||||
EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index));
|
||||
EXPECT_EQ(0u, span->firstChar);
|
||||
EXPECT_EQ(1u, span->lastChar);
|
||||
span++;
|
||||
const ResStringPool_span* span = test.styleAt(4);
|
||||
ASSERT_NE(nullptr, span);
|
||||
EXPECT_EQ(std::string("b"), util::getString(test, span->name.index));
|
||||
EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index));
|
||||
EXPECT_EQ(0u, span->firstChar);
|
||||
EXPECT_EQ(1u, span->lastChar);
|
||||
span++;
|
||||
|
||||
ASSERT_NE(ResStringPool_span::END, span->name.index);
|
||||
EXPECT_EQ(std::string("i"), util::getString(test, span->name.index));
|
||||
EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index));
|
||||
EXPECT_EQ(2u, span->firstChar);
|
||||
EXPECT_EQ(3u, span->lastChar);
|
||||
span++;
|
||||
ASSERT_NE(ResStringPool_span::END, span->name.index);
|
||||
EXPECT_EQ(std::string("i"), util::getString(test, span->name.index));
|
||||
EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index));
|
||||
EXPECT_EQ(2u, span->firstChar);
|
||||
EXPECT_EQ(3u, span->lastChar);
|
||||
span++;
|
||||
|
||||
EXPECT_EQ(ResStringPool_span::END, span->name.index);
|
||||
}
|
||||
EXPECT_EQ(ResStringPool_span::END, span->name.index);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,91 +17,94 @@
|
||||
#ifndef AAPT_VALUE_VISITOR_H
|
||||
#define AAPT_VALUE_VISITOR_H
|
||||
|
||||
#include "ResourceValues.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Visits a value and invokes the appropriate method based on its type. Does not traverse
|
||||
* Visits a value and invokes the appropriate method based on its type. Does not
|
||||
* traverse
|
||||
* into compound types. Use ValueVisitor for that.
|
||||
*/
|
||||
struct RawValueVisitor {
|
||||
virtual ~RawValueVisitor() = default;
|
||||
virtual ~RawValueVisitor() = default;
|
||||
|
||||
virtual void visitItem(Item* value) {}
|
||||
virtual void visit(Reference* value) { visitItem(value); }
|
||||
virtual void visit(RawString* value) { visitItem(value); }
|
||||
virtual void visit(String* value) { visitItem(value); }
|
||||
virtual void visit(StyledString* value) { visitItem(value); }
|
||||
virtual void visit(FileReference* value) { visitItem(value); }
|
||||
virtual void visit(Id* value) { visitItem(value); }
|
||||
virtual void visit(BinaryPrimitive* value) { visitItem(value); }
|
||||
virtual void visitItem(Item* value) {}
|
||||
virtual void visit(Reference* value) { visitItem(value); }
|
||||
virtual void visit(RawString* value) { visitItem(value); }
|
||||
virtual void visit(String* value) { visitItem(value); }
|
||||
virtual void visit(StyledString* value) { visitItem(value); }
|
||||
virtual void visit(FileReference* value) { visitItem(value); }
|
||||
virtual void visit(Id* value) { visitItem(value); }
|
||||
virtual void visit(BinaryPrimitive* value) { visitItem(value); }
|
||||
|
||||
virtual void visit(Attribute* value) {}
|
||||
virtual void visit(Style* value) {}
|
||||
virtual void visit(Array* value) {}
|
||||
virtual void visit(Plural* value) {}
|
||||
virtual void visit(Styleable* value) {}
|
||||
virtual void visit(Attribute* value) {}
|
||||
virtual void visit(Style* value) {}
|
||||
virtual void visit(Array* value) {}
|
||||
virtual void visit(Plural* value) {}
|
||||
virtual void visit(Styleable* value) {}
|
||||
};
|
||||
|
||||
// NOLINT, do not add parentheses around T.
|
||||
#define DECL_VISIT_COMPOUND_VALUE(T) \
|
||||
virtual void visit(T* value) { /* NOLINT */ \
|
||||
visitSubValues(value); \
|
||||
}
|
||||
#define DECL_VISIT_COMPOUND_VALUE(T) \
|
||||
virtual void visit(T* value) { /* NOLINT */ \
|
||||
visitSubValues(value); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits values, and if they are compound values, visits the components as well.
|
||||
* Visits values, and if they are compound values, visits the components as
|
||||
* well.
|
||||
*/
|
||||
struct ValueVisitor : public RawValueVisitor {
|
||||
// The compiler will think we're hiding an overload, when we actually intend
|
||||
// to call into RawValueVisitor. This will expose the visit methods in the super
|
||||
// class so the compiler knows we are trying to call them.
|
||||
using RawValueVisitor::visit;
|
||||
// The compiler will think we're hiding an overload, when we actually intend
|
||||
// to call into RawValueVisitor. This will expose the visit methods in the
|
||||
// super
|
||||
// class so the compiler knows we are trying to call them.
|
||||
using RawValueVisitor::visit;
|
||||
|
||||
void visitSubValues(Attribute* attribute) {
|
||||
for (Attribute::Symbol& symbol : attribute->symbols) {
|
||||
visit(&symbol.symbol);
|
||||
}
|
||||
void visitSubValues(Attribute* attribute) {
|
||||
for (Attribute::Symbol& symbol : attribute->symbols) {
|
||||
visit(&symbol.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Style* style) {
|
||||
if (style->parent) {
|
||||
visit(&style->parent.value());
|
||||
}
|
||||
|
||||
void visitSubValues(Style* style) {
|
||||
if (style->parent) {
|
||||
visit(&style->parent.value());
|
||||
}
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
visit(&entry.key);
|
||||
entry.value->accept(this);
|
||||
}
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
visit(&entry.key);
|
||||
entry.value->accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Array* array) {
|
||||
for (std::unique_ptr<Item>& item : array->items) {
|
||||
item->accept(this);
|
||||
}
|
||||
void visitSubValues(Array* array) {
|
||||
for (std::unique_ptr<Item>& item : array->items) {
|
||||
item->accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Plural* plural) {
|
||||
for (std::unique_ptr<Item>& item : plural->values) {
|
||||
if (item) {
|
||||
item->accept(this);
|
||||
}
|
||||
}
|
||||
void visitSubValues(Plural* plural) {
|
||||
for (std::unique_ptr<Item>& item : plural->values) {
|
||||
if (item) {
|
||||
item->accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Styleable* styleable) {
|
||||
for (Reference& reference : styleable->entries) {
|
||||
visit(&reference);
|
||||
}
|
||||
void visitSubValues(Styleable* styleable) {
|
||||
for (Reference& reference : styleable->entries) {
|
||||
visit(&reference);
|
||||
}
|
||||
}
|
||||
|
||||
DECL_VISIT_COMPOUND_VALUE(Attribute);
|
||||
DECL_VISIT_COMPOUND_VALUE(Style);
|
||||
DECL_VISIT_COMPOUND_VALUE(Array);
|
||||
DECL_VISIT_COMPOUND_VALUE(Plural);
|
||||
DECL_VISIT_COMPOUND_VALUE(Styleable);
|
||||
DECL_VISIT_COMPOUND_VALUE(Attribute);
|
||||
DECL_VISIT_COMPOUND_VALUE(Style);
|
||||
DECL_VISIT_COMPOUND_VALUE(Array);
|
||||
DECL_VISIT_COMPOUND_VALUE(Plural);
|
||||
DECL_VISIT_COMPOUND_VALUE(Styleable);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -109,11 +112,9 @@ struct ValueVisitor : public RawValueVisitor {
|
||||
*/
|
||||
template <typename T>
|
||||
struct DynCastVisitor : public RawValueVisitor {
|
||||
T* value = nullptr;
|
||||
T* value = nullptr;
|
||||
|
||||
void visit(T* v) override {
|
||||
value = v;
|
||||
}
|
||||
void visit(T* v) override { value = v; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -121,16 +122,14 @@ struct DynCastVisitor : public RawValueVisitor {
|
||||
*/
|
||||
template <>
|
||||
struct DynCastVisitor<Item> : public RawValueVisitor {
|
||||
Item* value = nullptr;
|
||||
Item* value = nullptr;
|
||||
|
||||
void visitItem(Item* item) override {
|
||||
value = item;
|
||||
}
|
||||
void visitItem(Item* item) override { value = item; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T* valueCast(const Value* value) {
|
||||
return valueCast<T>(const_cast<Value*>(value));
|
||||
return valueCast<T>(const_cast<Value*>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,30 +138,32 @@ const T* valueCast(const Value* value) {
|
||||
*/
|
||||
template <typename T>
|
||||
T* valueCast(Value* value) {
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
DynCastVisitor<T> visitor;
|
||||
value->accept(&visitor);
|
||||
return visitor.value;
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
DynCastVisitor<T> visitor;
|
||||
value->accept(&visitor);
|
||||
return visitor.value;
|
||||
}
|
||||
|
||||
inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
for (auto& configValue : entry->values) {
|
||||
configValue->value->accept(visitor);
|
||||
}
|
||||
}
|
||||
inline void visitAllValuesInPackage(ResourceTablePackage* pkg,
|
||||
RawValueVisitor* visitor) {
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
for (auto& configValue : entry->values) {
|
||||
configValue->value->accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
|
||||
for (auto& pkg : table->packages) {
|
||||
visitAllValuesInPackage(pkg.get(), visitor);
|
||||
}
|
||||
inline void visitAllValuesInTable(ResourceTable* table,
|
||||
RawValueVisitor* visitor) {
|
||||
for (auto& pkg : table->packages) {
|
||||
visitAllValuesInPackage(pkg.get(), visitor);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_VALUE_VISITOR_H
|
||||
#endif // AAPT_VALUE_VISITOR_H
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
@@ -24,63 +24,63 @@
|
||||
namespace aapt {
|
||||
|
||||
struct SingleReferenceVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
using ValueVisitor::visit;
|
||||
|
||||
Reference* visited = nullptr;
|
||||
Reference* visited = nullptr;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
visited = ref;
|
||||
}
|
||||
void visit(Reference* ref) override { visited = ref; }
|
||||
};
|
||||
|
||||
struct StyleVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
using ValueVisitor::visit;
|
||||
|
||||
std::list<Reference*> visitedRefs;
|
||||
Style* visitedStyle = nullptr;
|
||||
std::list<Reference*> visitedRefs;
|
||||
Style* visitedStyle = nullptr;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
visitedRefs.push_back(ref);
|
||||
}
|
||||
void visit(Reference* ref) override { visitedRefs.push_back(ref); }
|
||||
|
||||
void visit(Style* style) override {
|
||||
visitedStyle = style;
|
||||
ValueVisitor::visit(style);
|
||||
}
|
||||
void visit(Style* style) override {
|
||||
visitedStyle = style;
|
||||
ValueVisitor::visit(style);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ValueVisitorTest, VisitsReference) {
|
||||
Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
|
||||
SingleReferenceVisitor visitor;
|
||||
ref.accept(&visitor);
|
||||
Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
|
||||
SingleReferenceVisitor visitor;
|
||||
ref.accept(&visitor);
|
||||
|
||||
EXPECT_EQ(visitor.visited, &ref);
|
||||
EXPECT_EQ(visitor.visited, &ref);
|
||||
}
|
||||
|
||||
TEST(ValueVisitorTest, VisitsReferencesInStyle) {
|
||||
std::unique_ptr<Style> style = test::StyleBuilder()
|
||||
.setParent("android:style/foo")
|
||||
.addItem("android:attr/one", test::buildReference("android:id/foo"))
|
||||
.build();
|
||||
std::unique_ptr<Style> style =
|
||||
test::StyleBuilder()
|
||||
.setParent("android:style/foo")
|
||||
.addItem("android:attr/one", test::buildReference("android:id/foo"))
|
||||
.build();
|
||||
|
||||
StyleVisitor visitor;
|
||||
style->accept(&visitor);
|
||||
StyleVisitor visitor;
|
||||
style->accept(&visitor);
|
||||
|
||||
ASSERT_EQ(style.get(), visitor.visitedStyle);
|
||||
ASSERT_EQ(style.get(), visitor.visitedStyle);
|
||||
|
||||
// Entry attribute references, plus the parent reference, plus one value reference.
|
||||
ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
|
||||
// Entry attribute references, plus the parent reference, plus one value
|
||||
// reference.
|
||||
ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
|
||||
}
|
||||
|
||||
TEST(ValueVisitorTest, ValueCast) {
|
||||
std::unique_ptr<Reference> ref = test::buildReference("android:color/white");
|
||||
EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
|
||||
std::unique_ptr<Reference> ref = test::buildReference("android:color/white");
|
||||
EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
|
||||
|
||||
std::unique_ptr<Style> style = test::StyleBuilder()
|
||||
.addItem("android:attr/foo", test::buildReference("android:color/black"))
|
||||
.build();
|
||||
EXPECT_NE(valueCast<Style>(style.get()), nullptr);
|
||||
EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
|
||||
std::unique_ptr<Style> style =
|
||||
test::StyleBuilder()
|
||||
.addItem("android:attr/foo",
|
||||
test::buildReference("android:color/black"))
|
||||
.build();
|
||||
EXPECT_NE(valueCast<Style>(style.get()), nullptr);
|
||||
EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "compile/IdAssigner.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
@@ -25,178 +25,192 @@
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry,
|
||||
* Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and
|
||||
* ResourceEntry,
|
||||
* as long as there is no existing ID or the ID is the same.
|
||||
*/
|
||||
static bool assignId(IDiagnostics* diag, const ResourceId& id, const ResourceName& name,
|
||||
ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) {
|
||||
if (pkg->id.value() == id.packageId()) {
|
||||
if (!type->id || type->id.value() == id.typeId()) {
|
||||
type->id = id.typeId();
|
||||
static bool assignId(IDiagnostics* diag, const ResourceId& id,
|
||||
const ResourceName& name, ResourceTablePackage* pkg,
|
||||
ResourceTableType* type, ResourceEntry* entry) {
|
||||
if (pkg->id.value() == id.packageId()) {
|
||||
if (!type->id || type->id.value() == id.typeId()) {
|
||||
type->id = id.typeId();
|
||||
|
||||
if (!entry->id || entry->id.value() == id.entryId()) {
|
||||
entry->id = id.entryId();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!entry->id || entry->id.value() == id.entryId()) {
|
||||
entry->id = id.entryId();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ResourceId existingId(pkg->id.value(),
|
||||
type->id ? type->id.value() : 0,
|
||||
entry->id ? entry->id.value() : 0);
|
||||
diag->error(DiagMessage() << "can't assign ID " << id
|
||||
<< " to resource " << name
|
||||
<< " with conflicting ID " << existingId);
|
||||
return false;
|
||||
const ResourceId existingId(pkg->id.value(), type->id ? type->id.value() : 0,
|
||||
entry->id ? entry->id.value() : 0);
|
||||
diag->error(DiagMessage() << "can't assign ID " << id << " to resource "
|
||||
<< name << " with conflicting ID " << existingId);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
std::map<ResourceId, ResourceName> assignedIds;
|
||||
std::map<ResourceId, ResourceName> assignedIds;
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
const ResourceName name(package->name, type->type, entry->name);
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
const ResourceName name(package->name, type->type, entry->name);
|
||||
|
||||
if (mAssignedIdMap) {
|
||||
// Assign the pre-assigned stable ID meant for this resource.
|
||||
const auto iter = mAssignedIdMap->find(name);
|
||||
if (iter != mAssignedIdMap->end()) {
|
||||
const ResourceId assignedId = iter->second;
|
||||
const bool result = assignId(context->getDiagnostics(), assignedId, name,
|
||||
package.get(), type.get(), entry.get());
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (package->id && type->id && entry->id) {
|
||||
// If the ID is set for this resource, then reserve it.
|
||||
ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value());
|
||||
auto result = assignedIds.insert({ resourceId, name });
|
||||
const ResourceName& existingName = result.first->second;
|
||||
if (!result.second) {
|
||||
context->getDiagnostics()->error(DiagMessage() << "resource " << name
|
||||
<< " has same ID "
|
||||
<< resourceId
|
||||
<< " as " << existingName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mAssignedIdMap) {
|
||||
// Assign the pre-assigned stable ID meant for this resource.
|
||||
const auto iter = mAssignedIdMap->find(name);
|
||||
if (iter != mAssignedIdMap->end()) {
|
||||
const ResourceId assignedId = iter->second;
|
||||
const bool result =
|
||||
assignId(context->getDiagnostics(), assignedId, name,
|
||||
package.get(), type.get(), entry.get());
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mAssignedIdMap) {
|
||||
// Reserve all the IDs mentioned in the stable ID map. That way we won't assign
|
||||
// IDs that were listed in the map if they don't exist in the table.
|
||||
for (const auto& stableIdEntry : *mAssignedIdMap) {
|
||||
const ResourceName& preAssignedName = stableIdEntry.first;
|
||||
const ResourceId& preAssignedId = stableIdEntry.second;
|
||||
auto result = assignedIds.insert({ preAssignedId, preAssignedName });
|
||||
const ResourceName& existingName = result.first->second;
|
||||
if (!result.second && existingName != preAssignedName) {
|
||||
context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId
|
||||
<< " for resource " << preAssignedName
|
||||
<< " is already taken by resource "
|
||||
<< existingName);
|
||||
return false;
|
||||
}
|
||||
if (package->id && type->id && entry->id) {
|
||||
// If the ID is set for this resource, then reserve it.
|
||||
ResourceId resourceId(package->id.value(), type->id.value(),
|
||||
entry->id.value());
|
||||
auto result = assignedIds.insert({resourceId, name});
|
||||
const ResourceName& existingName = result.first->second;
|
||||
if (!result.second) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage() << "resource " << name << " has same ID "
|
||||
<< resourceId << " as " << existingName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign any resources without IDs the next available ID. Gaps will be filled if possible,
|
||||
// unless those IDs have been reserved.
|
||||
if (mAssignedIdMap) {
|
||||
// Reserve all the IDs mentioned in the stable ID map. That way we won't
|
||||
// assign
|
||||
// IDs that were listed in the map if they don't exist in the table.
|
||||
for (const auto& stableIdEntry : *mAssignedIdMap) {
|
||||
const ResourceName& preAssignedName = stableIdEntry.first;
|
||||
const ResourceId& preAssignedId = stableIdEntry.second;
|
||||
auto result = assignedIds.insert({preAssignedId, preAssignedName});
|
||||
const ResourceName& existingName = result.first->second;
|
||||
if (!result.second && existingName != preAssignedName) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage() << "stable ID " << preAssignedId << " for resource "
|
||||
<< preAssignedName << " is already taken by resource "
|
||||
<< existingName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto assignedIdsIterEnd = assignedIds.end();
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
// Assign any resources without IDs the next available ID. Gaps will be filled
|
||||
// if possible,
|
||||
// unless those IDs have been reserved.
|
||||
|
||||
// Build a half filled ResourceId object, which will be used to find the closest matching
|
||||
// reserved ID in the assignedId map. From that point the next available type ID can be
|
||||
// found.
|
||||
ResourceId resourceId(package->id.value(), 0, 0);
|
||||
uint8_t nextExpectedTypeId = 1;
|
||||
const auto assignedIdsIterEnd = assignedIds.end();
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
|
||||
// Find the closest matching ResourceId that is <= the one with only the package set.
|
||||
auto nextTypeIter = assignedIds.lower_bound(resourceId);
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
// We need to assign a type ID. Iterate over the reserved IDs until we find
|
||||
// some type ID that is a distance of 2 greater than the last one we've seen.
|
||||
// That means there is an available type ID between these reserved IDs.
|
||||
while (nextTypeIter != assignedIdsIterEnd) {
|
||||
if (nextTypeIter->first.packageId() != package->id.value()) {
|
||||
break;
|
||||
}
|
||||
// Build a half filled ResourceId object, which will be used to find the
|
||||
// closest matching
|
||||
// reserved ID in the assignedId map. From that point the next available
|
||||
// type ID can be
|
||||
// found.
|
||||
ResourceId resourceId(package->id.value(), 0, 0);
|
||||
uint8_t nextExpectedTypeId = 1;
|
||||
|
||||
const uint8_t typeId = nextTypeIter->first.typeId();
|
||||
if (typeId > nextExpectedTypeId) {
|
||||
// There is a gap in the type IDs, so use the missing one.
|
||||
type->id = nextExpectedTypeId++;
|
||||
break;
|
||||
}
|
||||
// Find the closest matching ResourceId that is <= the one with only the
|
||||
// package set.
|
||||
auto nextTypeIter = assignedIds.lower_bound(resourceId);
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
// We need to assign a type ID. Iterate over the reserved IDs until we
|
||||
// find
|
||||
// some type ID that is a distance of 2 greater than the last one we've
|
||||
// seen.
|
||||
// That means there is an available type ID between these reserved IDs.
|
||||
while (nextTypeIter != assignedIdsIterEnd) {
|
||||
if (nextTypeIter->first.packageId() != package->id.value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Set our expectation to be the next type ID after the reserved one we
|
||||
// just saw.
|
||||
nextExpectedTypeId = typeId + 1;
|
||||
const uint8_t typeId = nextTypeIter->first.typeId();
|
||||
if (typeId > nextExpectedTypeId) {
|
||||
// There is a gap in the type IDs, so use the missing one.
|
||||
type->id = nextExpectedTypeId++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to the next reserved ID.
|
||||
++nextTypeIter;
|
||||
}
|
||||
// Set our expectation to be the next type ID after the reserved one
|
||||
// we
|
||||
// just saw.
|
||||
nextExpectedTypeId = typeId + 1;
|
||||
|
||||
if (!type->id) {
|
||||
// We must have hit the end of the reserved IDs and not found a gap.
|
||||
// That means the next ID is available.
|
||||
type->id = nextExpectedTypeId++;
|
||||
}
|
||||
}
|
||||
|
||||
resourceId = ResourceId(package->id.value(), type->id.value(), 0);
|
||||
uint16_t nextExpectedEntryId = 0;
|
||||
|
||||
// Find the closest matching ResourceId that is <= the one with only the package
|
||||
// and type set.
|
||||
auto nextEntryIter = assignedIds.lower_bound(resourceId);
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
// We need to assign an entry ID. Iterate over the reserved IDs until we find
|
||||
// some entry ID that is a distance of 2 greater than the last one we've seen.
|
||||
// That means there is an available entry ID between these reserved IDs.
|
||||
while (nextEntryIter != assignedIdsIterEnd) {
|
||||
if (nextEntryIter->first.packageId() != package->id.value() ||
|
||||
nextEntryIter->first.typeId() != type->id.value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint16_t entryId = nextEntryIter->first.entryId();
|
||||
if (entryId > nextExpectedEntryId) {
|
||||
// There is a gap in the entry IDs, so use the missing one.
|
||||
entry->id = nextExpectedEntryId++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set our expectation to be the next type ID after the reserved one we
|
||||
// just saw.
|
||||
nextExpectedEntryId = entryId + 1;
|
||||
|
||||
// Move to the next reserved entry ID.
|
||||
++nextEntryIter;
|
||||
}
|
||||
|
||||
if (!entry->id) {
|
||||
// We must have hit the end of the reserved IDs and not found a gap.
|
||||
// That means the next ID is available.
|
||||
entry->id = nextExpectedEntryId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move to the next reserved ID.
|
||||
++nextTypeIter;
|
||||
}
|
||||
|
||||
if (!type->id) {
|
||||
// We must have hit the end of the reserved IDs and not found a gap.
|
||||
// That means the next ID is available.
|
||||
type->id = nextExpectedTypeId++;
|
||||
}
|
||||
}
|
||||
|
||||
resourceId = ResourceId(package->id.value(), type->id.value(), 0);
|
||||
uint16_t nextExpectedEntryId = 0;
|
||||
|
||||
// Find the closest matching ResourceId that is <= the one with only the
|
||||
// package
|
||||
// and type set.
|
||||
auto nextEntryIter = assignedIds.lower_bound(resourceId);
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
// We need to assign an entry ID. Iterate over the reserved IDs until
|
||||
// we find
|
||||
// some entry ID that is a distance of 2 greater than the last one
|
||||
// we've seen.
|
||||
// That means there is an available entry ID between these reserved
|
||||
// IDs.
|
||||
while (nextEntryIter != assignedIdsIterEnd) {
|
||||
if (nextEntryIter->first.packageId() != package->id.value() ||
|
||||
nextEntryIter->first.typeId() != type->id.value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint16_t entryId = nextEntryIter->first.entryId();
|
||||
if (entryId > nextExpectedEntryId) {
|
||||
// There is a gap in the entry IDs, so use the missing one.
|
||||
entry->id = nextExpectedEntryId++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set our expectation to be the next type ID after the reserved one
|
||||
// we
|
||||
// just saw.
|
||||
nextExpectedEntryId = entryId + 1;
|
||||
|
||||
// Move to the next reserved entry ID.
|
||||
++nextEntryIter;
|
||||
}
|
||||
|
||||
if (!entry->id) {
|
||||
// We must have hit the end of the reserved IDs and not found a gap.
|
||||
// That means the next ID is available.
|
||||
entry->id = nextExpectedEntryId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -26,22 +26,22 @@
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
|
||||
* Assigns IDs to each resource in the table, respecting existing IDs and
|
||||
* filling in gaps
|
||||
* in between fixed ID assignments.
|
||||
*/
|
||||
class IdAssigner : public IResourceTableConsumer {
|
||||
public:
|
||||
IdAssigner() = default;
|
||||
explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) :
|
||||
mAssignedIdMap(map) {
|
||||
}
|
||||
public:
|
||||
IdAssigner() = default;
|
||||
explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map)
|
||||
: mAssignedIdMap(map) {}
|
||||
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
|
||||
private:
|
||||
const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
|
||||
private:
|
||||
const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_COMPILE_IDASSIGNER_H */
|
||||
|
||||
@@ -22,154 +22,163 @@ namespace aapt {
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table);
|
||||
|
||||
TEST(IdAssignerTest, AssignIds) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo")
|
||||
.addSimple("android:attr/bar")
|
||||
.addSimple("android:id/foo")
|
||||
.setPackageId("android", 0x01)
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo")
|
||||
.addSimple("android:attr/bar")
|
||||
.addSimple("android:id/foo")
|
||||
.setPackageId("android", 0x01)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
}
|
||||
|
||||
TEST(IdAssignerTest, AssignIdsWithReservedIds) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:id/foo", ResourceId(0x01010000))
|
||||
.addSimple("android:dimen/two")
|
||||
.addSimple("android:integer/three")
|
||||
.addSimple("android:string/five")
|
||||
.addSimple("android:attr/fun", ResourceId(0x01040000))
|
||||
.addSimple("android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple("android:attr/bar")
|
||||
.addSimple("android:attr/baz")
|
||||
.addSimple("app:id/biz")
|
||||
.setPackageId("android", 0x01)
|
||||
.setPackageId("app", 0x7f)
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.addSimple("android:id/foo", ResourceId(0x01010000))
|
||||
.addSimple("android:dimen/two")
|
||||
.addSimple("android:integer/three")
|
||||
.addSimple("android:string/five")
|
||||
.addSimple("android:attr/fun", ResourceId(0x01040000))
|
||||
.addSimple("android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple("android:attr/bar")
|
||||
.addSimple("android:attr/baz")
|
||||
.addSimple("app:id/biz")
|
||||
.setPackageId("android", 0x01)
|
||||
.setPackageId("app", 0x7f)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> maybeResult;
|
||||
Maybe<ResourceTable::SearchResult> maybeResult;
|
||||
|
||||
// Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
|
||||
// Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
|
||||
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
|
||||
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:integer/three"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
|
||||
maybeResult =
|
||||
table->findResource(test::parseNameOrDie("android:integer/three"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
|
||||
|
||||
// Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX IDs.
|
||||
// Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
|
||||
// IDs.
|
||||
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:string/five"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
|
||||
maybeResult =
|
||||
table->findResource(test::parseNameOrDie("android:string/five"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
|
||||
|
||||
// Expect to fill in the gaps between 0x01040000 and 0x01040006.
|
||||
// Expect to fill in the gaps between 0x01040000 and 0x01040006.
|
||||
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
|
||||
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
|
||||
maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
|
||||
AAPT_ASSERT_TRUE(maybeResult);
|
||||
EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
|
||||
}
|
||||
|
||||
TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple("android:attr/bar", ResourceId(0x01040006))
|
||||
.setPackageId("android", 0x01)
|
||||
.setPackageId("app", 0x7f)
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo", ResourceId(0x01040006))
|
||||
.addSimple("android:attr/bar", ResourceId(0x01040006))
|
||||
.setPackageId("android", 0x01)
|
||||
.setPackageId("app", 0x7f)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
IdAssigner assigner;
|
||||
|
||||
ASSERT_FALSE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_FALSE(assigner.consume(context.get(), table.get()));
|
||||
}
|
||||
|
||||
TEST(IdAssignerTest, AssignIdsWithIdMap) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo")
|
||||
.addSimple("android:attr/bar")
|
||||
.setPackageId("android", 0x01)
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/foo")
|
||||
.addSimple("android:attr/bar")
|
||||
.setPackageId("android", 0x01)
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unordered_map<ResourceName, ResourceId> idMap = {
|
||||
{ test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002) } };
|
||||
IdAssigner assigner(&idMap);
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(
|
||||
test::parseNameOrDie("android:attr/foo"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unordered_map<ResourceName, ResourceId> idMap = {
|
||||
{test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
|
||||
IdAssigner assigner(&idMap);
|
||||
ASSERT_TRUE(assigner.consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(verifyIds(table.get()));
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
table->findResource(test::parseNameOrDie("android:attr/foo"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
|
||||
const ResourceTable::SearchResult& searchResult = result.value();
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
|
||||
EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
|
||||
const ResourceTable::SearchResult& searchResult = result.value();
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
|
||||
EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
|
||||
}
|
||||
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table) {
|
||||
std::set<uint8_t> packageIds;
|
||||
for (auto& package : table->packages) {
|
||||
if (!package->id) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!packageIds.insert(package->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name
|
||||
<< " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
|
||||
}
|
||||
std::set<uint8_t> packageIds;
|
||||
for (auto& package : table->packages) {
|
||||
if (!package->id) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name
|
||||
<< " has no ID";
|
||||
}
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::set<uint8_t> typeIds;
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type << " of package "
|
||||
<< package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!typeIds.insert(type->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type
|
||||
<< " of package " << package->name << " has non-unique ID "
|
||||
<< std::hex << (int) type->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (auto& type : package->types) {
|
||||
std::set<uint16_t> entryIds;
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
|
||||
<< type->type << " of package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!entryIds.insert(entry->id.value()).second) {
|
||||
return ::testing::AssertionFailure() << "entry " << entry->name
|
||||
<< " of type " << type->type << " of package " << package->name
|
||||
<< " has non-unique ID "
|
||||
<< std::hex << (int) entry->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!packageIds.insert(package->id.value()).second) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "package " << package->name << " has non-unique ID " << std::hex
|
||||
<< (int)package->id.value() << std::dec;
|
||||
}
|
||||
return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
|
||||
}
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::set<uint8_t> typeIds;
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type
|
||||
<< " of package " << package->name
|
||||
<< " has no ID";
|
||||
}
|
||||
|
||||
if (!typeIds.insert(type->id.value()).second) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "type " << type->type << " of package " << package->name
|
||||
<< " has non-unique ID " << std::hex << (int)type->id.value()
|
||||
<< std::dec;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& type : package->types) {
|
||||
std::set<uint16_t> entryIds;
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "entry " << entry->name << " of type " << type->type
|
||||
<< " of package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!entryIds.insert(entry->id.value()).second) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "entry " << entry->name << " of type " << type->type
|
||||
<< " of package " << package->name << " has non-unique ID "
|
||||
<< std::hex << (int)entry->id.value() << std::dec;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -29,173 +29,180 @@ namespace aapt {
|
||||
* An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
|
||||
*/
|
||||
class Image {
|
||||
public:
|
||||
explicit Image() = default;
|
||||
public:
|
||||
explicit Image() = default;
|
||||
|
||||
/**
|
||||
* A `height` sized array of pointers, where each element points to a
|
||||
* `width` sized row of RGBA_8888 pixels.
|
||||
*/
|
||||
std::unique_ptr<uint8_t*[]> rows;
|
||||
/**
|
||||
* A `height` sized array of pointers, where each element points to a
|
||||
* `width` sized row of RGBA_8888 pixels.
|
||||
*/
|
||||
std::unique_ptr<uint8_t* []> rows;
|
||||
|
||||
/**
|
||||
* The width of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
|
||||
* format limitations.
|
||||
*/
|
||||
int32_t width = 0;
|
||||
/**
|
||||
* The width of the image in RGBA_8888 pixels. This is int32_t because of
|
||||
* 9-patch data
|
||||
* format limitations.
|
||||
*/
|
||||
int32_t width = 0;
|
||||
|
||||
/**
|
||||
* The height of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
|
||||
* format limitations.
|
||||
*/
|
||||
int32_t height = 0;
|
||||
/**
|
||||
* The height of the image in RGBA_8888 pixels. This is int32_t because of
|
||||
* 9-patch data
|
||||
* format limitations.
|
||||
*/
|
||||
int32_t height = 0;
|
||||
|
||||
/**
|
||||
* Buffer to the raw image data stored sequentially.
|
||||
* Use `rows` to access the data on a row-by-row basis.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
/**
|
||||
* Buffer to the raw image data stored sequentially.
|
||||
* Use `rows` to access the data on a row-by-row basis.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Image);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Image);
|
||||
};
|
||||
|
||||
/**
|
||||
* A range of pixel values, starting at 'start' and ending before 'end' exclusive. Or rather [a, b).
|
||||
* A range of pixel values, starting at 'start' and ending before 'end'
|
||||
* exclusive. Or rather [a, b).
|
||||
*/
|
||||
struct Range {
|
||||
int32_t start = 0;
|
||||
int32_t end = 0;
|
||||
int32_t start = 0;
|
||||
int32_t end = 0;
|
||||
|
||||
explicit Range() = default;
|
||||
inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
|
||||
}
|
||||
explicit Range() = default;
|
||||
inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
|
||||
};
|
||||
|
||||
inline bool operator==(const Range& left, const Range& right) {
|
||||
return left.start == right.start && left.end == right.end;
|
||||
return left.start == right.start && left.end == right.end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inset lengths from all edges of a rectangle. `left` and `top` are measured from the left and top
|
||||
* edges, while `right` and `bottom` are measured from the right and bottom edges, respectively.
|
||||
* Inset lengths from all edges of a rectangle. `left` and `top` are measured
|
||||
* from the left and top
|
||||
* edges, while `right` and `bottom` are measured from the right and bottom
|
||||
* edges, respectively.
|
||||
*/
|
||||
struct Bounds {
|
||||
int32_t left = 0;
|
||||
int32_t top = 0;
|
||||
int32_t right = 0;
|
||||
int32_t bottom = 0;
|
||||
int32_t left = 0;
|
||||
int32_t top = 0;
|
||||
int32_t right = 0;
|
||||
int32_t bottom = 0;
|
||||
|
||||
explicit Bounds() = default;
|
||||
inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) :
|
||||
left(l), top(t), right(r), bottom(b) {
|
||||
}
|
||||
explicit Bounds() = default;
|
||||
inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
|
||||
: left(l), top(t), right(r), bottom(b) {}
|
||||
|
||||
bool nonZero() const;
|
||||
bool nonZero() const;
|
||||
};
|
||||
|
||||
inline bool Bounds::nonZero() const {
|
||||
return left != 0 || top != 0 || right != 0 || bottom != 0;
|
||||
return left != 0 || top != 0 || right != 0 || bottom != 0;
|
||||
}
|
||||
|
||||
inline bool operator==(const Bounds& left, const Bounds& right) {
|
||||
return left.left == right.left && left.top == right.top &&
|
||||
left.right == right.right && left.bottom == right.bottom;
|
||||
return left.left == right.left && left.top == right.top &&
|
||||
left.right == right.right && left.bottom == right.bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains 9-patch data from a source image. All measurements exclude the 1px border of the
|
||||
* Contains 9-patch data from a source image. All measurements exclude the 1px
|
||||
* border of the
|
||||
* source 9-patch image.
|
||||
*/
|
||||
class NinePatch {
|
||||
public:
|
||||
static std::unique_ptr<NinePatch> create(uint8_t** rows,
|
||||
const int32_t width, const int32_t height,
|
||||
std::string* errOut);
|
||||
public:
|
||||
static std::unique_ptr<NinePatch> create(uint8_t** rows, const int32_t width,
|
||||
const int32_t height,
|
||||
std::string* errOut);
|
||||
|
||||
/**
|
||||
* Packs the RGBA_8888 data pointed to by pixel into a uint32_t
|
||||
* with format 0xAARRGGBB (the way 9-patch expects it).
|
||||
*/
|
||||
static uint32_t packRGBA(const uint8_t* pixel);
|
||||
/**
|
||||
* Packs the RGBA_8888 data pointed to by pixel into a uint32_t
|
||||
* with format 0xAARRGGBB (the way 9-patch expects it).
|
||||
*/
|
||||
static uint32_t packRGBA(const uint8_t* pixel);
|
||||
|
||||
/**
|
||||
* 9-patch content padding/insets. All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
Bounds padding;
|
||||
/**
|
||||
* 9-patch content padding/insets. All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
Bounds padding;
|
||||
|
||||
/**
|
||||
* Optical layout bounds/insets. This overrides the padding for
|
||||
* layout purposes. All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
* See https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
|
||||
*/
|
||||
Bounds layoutBounds;
|
||||
/**
|
||||
* Optical layout bounds/insets. This overrides the padding for
|
||||
* layout purposes. All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
* See
|
||||
* https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
|
||||
*/
|
||||
Bounds layoutBounds;
|
||||
|
||||
/**
|
||||
* Outline of the image, calculated based on opacity.
|
||||
*/
|
||||
Bounds outline;
|
||||
/**
|
||||
* Outline of the image, calculated based on opacity.
|
||||
*/
|
||||
Bounds outline;
|
||||
|
||||
/**
|
||||
* The computed radius of the outline. If non-zero, the outline is a rounded-rect.
|
||||
*/
|
||||
float outlineRadius = 0.0f;
|
||||
/**
|
||||
* The computed radius of the outline. If non-zero, the outline is a
|
||||
* rounded-rect.
|
||||
*/
|
||||
float outlineRadius = 0.0f;
|
||||
|
||||
/**
|
||||
* The largest alpha value within the outline.
|
||||
*/
|
||||
uint32_t outlineAlpha = 0x000000ffu;
|
||||
/**
|
||||
* The largest alpha value within the outline.
|
||||
*/
|
||||
uint32_t outlineAlpha = 0x000000ffu;
|
||||
|
||||
/**
|
||||
* Horizontal regions of the image that are stretchable.
|
||||
* All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
std::vector<Range> horizontalStretchRegions;
|
||||
/**
|
||||
* Horizontal regions of the image that are stretchable.
|
||||
* All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
std::vector<Range> horizontalStretchRegions;
|
||||
|
||||
/**
|
||||
* Vertical regions of the image that are stretchable.
|
||||
* All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
std::vector<Range> verticalStretchRegions;
|
||||
/**
|
||||
* Vertical regions of the image that are stretchable.
|
||||
* All positions are relative to the 9-patch
|
||||
* NOT including the 1px thick source border.
|
||||
*/
|
||||
std::vector<Range> verticalStretchRegions;
|
||||
|
||||
/**
|
||||
* The colors within each region, fixed or stretchable.
|
||||
* For w*h regions, the color of region (x,y) is addressable
|
||||
* via index y*w + x.
|
||||
*/
|
||||
std::vector<uint32_t> regionColors;
|
||||
/**
|
||||
* The colors within each region, fixed or stretchable.
|
||||
* For w*h regions, the color of region (x,y) is addressable
|
||||
* via index y*w + x.
|
||||
*/
|
||||
std::vector<uint32_t> regionColors;
|
||||
|
||||
/**
|
||||
* Returns serialized data containing the original basic 9-patch meta data.
|
||||
* Optical layout bounds and round rect outline data must be serialized
|
||||
* separately using serializeOpticalLayoutBounds() and serializeRoundedRectOutline().
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
|
||||
/**
|
||||
* Returns serialized data containing the original basic 9-patch meta data.
|
||||
* Optical layout bounds and round rect outline data must be serialized
|
||||
* separately using serializeOpticalLayoutBounds() and
|
||||
* serializeRoundedRectOutline().
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
|
||||
|
||||
/**
|
||||
* Serializes the layout bounds.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
|
||||
/**
|
||||
* Serializes the layout bounds.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
|
||||
|
||||
/**
|
||||
* Serializes the rounded-rect outline.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
|
||||
/**
|
||||
* Serializes the rounded-rect outline.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
|
||||
|
||||
private:
|
||||
explicit NinePatch() = default;
|
||||
private:
|
||||
explicit NinePatch() = default;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NinePatch);
|
||||
DISALLOW_COPY_AND_ASSIGN(NinePatch);
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& out, const Range& range);
|
||||
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
|
||||
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch);
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_COMPILE_IMAGE_H */
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compile/InlineXmlFormatParser.h"
|
||||
#include "Debug.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "compile/InlineXmlFormatParser.h"
|
||||
#include "util/Util.h"
|
||||
#include "xml/XmlDom.h"
|
||||
#include "xml/XmlUtil.h"
|
||||
@@ -33,158 +33,172 @@ namespace {
|
||||
* XML Visitor that will find all <aapt:attr> elements for extraction.
|
||||
*/
|
||||
class Visitor : public xml::PackageAwareVisitor {
|
||||
public:
|
||||
using xml::PackageAwareVisitor::visit;
|
||||
public:
|
||||
using xml::PackageAwareVisitor::visit;
|
||||
|
||||
struct InlineDeclaration {
|
||||
xml::Element* el;
|
||||
std::string attrNamespaceUri;
|
||||
std::string attrName;
|
||||
};
|
||||
struct InlineDeclaration {
|
||||
xml::Element* el;
|
||||
std::string attrNamespaceUri;
|
||||
std::string attrName;
|
||||
};
|
||||
|
||||
explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) :
|
||||
mContext(context), mXmlResource(xmlResource) {
|
||||
explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource)
|
||||
: mContext(context), mXmlResource(xmlResource) {}
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
|
||||
xml::PackageAwareVisitor::visit(el);
|
||||
return;
|
||||
}
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
|
||||
xml::PackageAwareVisitor::visit(el);
|
||||
return;
|
||||
}
|
||||
const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
|
||||
|
||||
const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
|
||||
|
||||
xml::Attribute* attr = el->findAttribute({}, "name");
|
||||
if (!attr) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
|
||||
if (!ref) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '"
|
||||
<< attr->value << "'");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const ResourceName& name = ref.value().name.value();
|
||||
|
||||
// Use an empty string for the compilation package because we don't want to default to
|
||||
// the local package if the user specified name="style" or something. This should just
|
||||
// be the default namespace.
|
||||
Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {});
|
||||
if (!maybePkg) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '"
|
||||
<< name.package << "'");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const xml::ExtractedPackage& pkg = maybePkg.value();
|
||||
const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference;
|
||||
|
||||
InlineDeclaration decl;
|
||||
decl.el = el;
|
||||
decl.attrName = name.entry;
|
||||
if (!pkg.package.empty()) {
|
||||
decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace);
|
||||
}
|
||||
|
||||
mInlineDeclarations.push_back(std::move(decl));
|
||||
xml::Attribute* attr = el->findAttribute({}, "name");
|
||||
if (!attr) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src)
|
||||
<< "missing 'name' attribute");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<InlineDeclaration>& getInlineDeclarations() const {
|
||||
return mInlineDeclarations;
|
||||
Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
|
||||
if (!ref) {
|
||||
mContext->getDiagnostics()->error(
|
||||
DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasError() const {
|
||||
return mError;
|
||||
const ResourceName& name = ref.value().name.value();
|
||||
|
||||
// Use an empty string for the compilation package because we don't want to
|
||||
// default to
|
||||
// the local package if the user specified name="style" or something. This
|
||||
// should just
|
||||
// be the default namespace.
|
||||
Maybe<xml::ExtractedPackage> maybePkg =
|
||||
transformPackageAlias(name.package, {});
|
||||
if (!maybePkg) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src)
|
||||
<< "invalid namespace prefix '"
|
||||
<< name.package << "'");
|
||||
mError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Visitor);
|
||||
const xml::ExtractedPackage& pkg = maybePkg.value();
|
||||
const bool privateNamespace =
|
||||
pkg.privateNamespace || ref.value().privateReference;
|
||||
|
||||
IAaptContext* mContext;
|
||||
xml::XmlResource* mXmlResource;
|
||||
std::vector<InlineDeclaration> mInlineDeclarations;
|
||||
bool mError = false;
|
||||
InlineDeclaration decl;
|
||||
decl.el = el;
|
||||
decl.attrName = name.entry;
|
||||
if (!pkg.package.empty()) {
|
||||
decl.attrNamespaceUri =
|
||||
xml::buildPackageNamespace(pkg.package, privateNamespace);
|
||||
}
|
||||
|
||||
mInlineDeclarations.push_back(std::move(decl));
|
||||
}
|
||||
|
||||
const std::vector<InlineDeclaration>& getInlineDeclarations() const {
|
||||
return mInlineDeclarations;
|
||||
}
|
||||
|
||||
bool hasError() const { return mError; }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Visitor);
|
||||
|
||||
IAaptContext* mContext;
|
||||
xml::XmlResource* mXmlResource;
|
||||
std::vector<InlineDeclaration> mInlineDeclarations;
|
||||
bool mError = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) {
|
||||
Visitor visitor(context, doc);
|
||||
doc->root->accept(&visitor);
|
||||
if (visitor.hasError()) {
|
||||
bool InlineXmlFormatParser::consume(IAaptContext* context,
|
||||
xml::XmlResource* doc) {
|
||||
Visitor visitor(context, doc);
|
||||
doc->root->accept(&visitor);
|
||||
if (visitor.hasError()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t nameSuffixCounter = 0;
|
||||
for (const Visitor::InlineDeclaration& decl :
|
||||
visitor.getInlineDeclarations()) {
|
||||
auto newDoc = util::make_unique<xml::XmlResource>();
|
||||
newDoc->file.config = doc->file.config;
|
||||
newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
|
||||
newDoc->file.name = doc->file.name;
|
||||
|
||||
// Modify the new entry name. We need to suffix the entry with a number to
|
||||
// avoid
|
||||
// local collisions, then mangle it with the empty package, such that it
|
||||
// won't show up
|
||||
// in R.java.
|
||||
|
||||
newDoc->file.name.entry = NameMangler::mangleEntry(
|
||||
{}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
|
||||
|
||||
// Extracted elements must be the only child of <aapt:attr>.
|
||||
// Make sure there is one root node in the children (ignore empty text).
|
||||
for (auto& child : decl.el->children) {
|
||||
const Source childSource = doc->file.source.withLine(child->lineNumber);
|
||||
if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
|
||||
if (!util::trimWhitespace(t->text).empty()) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(childSource)
|
||||
<< "can't extract text into its own resource");
|
||||
return false;
|
||||
}
|
||||
} else if (newDoc->root) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(childSource)
|
||||
<< "inline XML resources must have a single root");
|
||||
return false;
|
||||
} else {
|
||||
newDoc->root = std::move(child);
|
||||
newDoc->root->parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t nameSuffixCounter = 0;
|
||||
for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) {
|
||||
auto newDoc = util::make_unique<xml::XmlResource>();
|
||||
newDoc->file.config = doc->file.config;
|
||||
newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
|
||||
newDoc->file.name = doc->file.name;
|
||||
|
||||
// Modify the new entry name. We need to suffix the entry with a number to avoid
|
||||
// local collisions, then mangle it with the empty package, such that it won't show up
|
||||
// in R.java.
|
||||
|
||||
newDoc->file.name.entry = NameMangler::mangleEntry(
|
||||
{}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
|
||||
|
||||
// Extracted elements must be the only child of <aapt:attr>.
|
||||
// Make sure there is one root node in the children (ignore empty text).
|
||||
for (auto& child : decl.el->children) {
|
||||
const Source childSource = doc->file.source.withLine(child->lineNumber);
|
||||
if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
|
||||
if (!util::trimWhitespace(t->text).empty()) {
|
||||
context->getDiagnostics()->error(DiagMessage(childSource)
|
||||
<< "can't extract text into its own resource");
|
||||
return false;
|
||||
}
|
||||
} else if (newDoc->root) {
|
||||
context->getDiagnostics()->error(DiagMessage(childSource)
|
||||
<< "inline XML resources must have a single root");
|
||||
return false;
|
||||
} else {
|
||||
newDoc->root = std::move(child);
|
||||
newDoc->root->parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk up and find the parent element.
|
||||
xml::Node* node = decl.el;
|
||||
xml::Element* parentEl = nullptr;
|
||||
while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
if (!parentEl) {
|
||||
context->getDiagnostics()->error(DiagMessage(newDoc->file.source)
|
||||
<< "no suitable parent for inheriting attribute");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the inline attribute to the parent.
|
||||
parentEl->attributes.push_back(xml::Attribute{
|
||||
decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() });
|
||||
|
||||
// Delete the subtree.
|
||||
for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) {
|
||||
if (iter->get() == node) {
|
||||
parentEl->children.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mQueue.push_back(std::move(newDoc));
|
||||
|
||||
nameSuffixCounter++;
|
||||
// Walk up and find the parent element.
|
||||
xml::Node* node = decl.el;
|
||||
xml::Element* parentEl = nullptr;
|
||||
while (node->parent &&
|
||||
(parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
|
||||
node = node->parent;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (!parentEl) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(newDoc->file.source)
|
||||
<< "no suitable parent for inheriting attribute");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the inline attribute to the parent.
|
||||
parentEl->attributes.push_back(
|
||||
xml::Attribute{decl.attrNamespaceUri, decl.attrName,
|
||||
"@" + newDoc->file.name.toString()});
|
||||
|
||||
// Delete the subtree.
|
||||
for (auto iter = parentEl->children.begin();
|
||||
iter != parentEl->children.end(); ++iter) {
|
||||
if (iter->get() == node) {
|
||||
parentEl->children.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mQueue.push_back(std::move(newDoc));
|
||||
|
||||
nameSuffixCounter++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -41,25 +41,28 @@ namespace aapt {
|
||||
* </aapt:attr>
|
||||
* </animated-vector>
|
||||
*
|
||||
* The <vector> will be extracted into its own XML file and <animated-vector> will
|
||||
* gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.
|
||||
* The <vector> will be extracted into its own XML file and <animated-vector>
|
||||
* will
|
||||
* gain an attribute 'android:drawable' set to a reference to the extracted
|
||||
* <vector> resource.
|
||||
*/
|
||||
class InlineXmlFormatParser : public IXmlResourceConsumer {
|
||||
public:
|
||||
explicit InlineXmlFormatParser() = default;
|
||||
public:
|
||||
explicit InlineXmlFormatParser() = default;
|
||||
|
||||
bool consume(IAaptContext* context, xml::XmlResource* doc) override;
|
||||
bool consume(IAaptContext* context, xml::XmlResource* doc) override;
|
||||
|
||||
std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() {
|
||||
return mQueue;
|
||||
}
|
||||
std::vector<std::unique_ptr<xml::XmlResource>>&
|
||||
getExtractedInlineXmlDocuments() {
|
||||
return mQueue;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
|
||||
|
||||
std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
|
||||
std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */
|
||||
|
||||
@@ -20,22 +20,22 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(InlineXmlFormatParserTest, PassThrough) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<View android:text="hey">
|
||||
<View android:id="hi" />
|
||||
</View>
|
||||
</View>)EOF");
|
||||
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
|
||||
}
|
||||
|
||||
TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:text">
|
||||
@@ -45,47 +45,48 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
|
||||
</aapt:attr>
|
||||
</View1>)EOF");
|
||||
|
||||
doc->file.name = test::parseNameOrDie("layout/main");
|
||||
doc->file.name = test::parseNameOrDie("layout/main");
|
||||
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
|
||||
// One XML resource should have been extracted.
|
||||
EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
|
||||
// One XML resource should have been extracted.
|
||||
EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
|
||||
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
EXPECT_EQ("View1", el->name);
|
||||
EXPECT_EQ("View1", el->name);
|
||||
|
||||
// The <aapt:attr> tag should be extracted.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
// The <aapt:attr> tag should be extracted.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
|
||||
// The 'android:text' attribute should be set with a reference.
|
||||
xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
// The 'android:text' attribute should be set with a reference.
|
||||
xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
|
||||
ResourceNameRef nameRef;
|
||||
ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
|
||||
ResourceNameRef nameRef;
|
||||
ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
|
||||
|
||||
xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDoc);
|
||||
xml::XmlResource* extractedDoc =
|
||||
parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDoc);
|
||||
|
||||
// Make sure the generated reference is correct.
|
||||
EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
|
||||
EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
|
||||
EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
|
||||
// Make sure the generated reference is correct.
|
||||
EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
|
||||
EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
|
||||
EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
|
||||
|
||||
// Verify the structure of the extracted XML.
|
||||
el = xml::findRootElement(extractedDoc);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
EXPECT_NE(nullptr, el->findChild({}, "View3"));
|
||||
// Verify the structure of the extracted XML.
|
||||
el = xml::findRootElement(extractedDoc);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
EXPECT_NE(nullptr, el->findChild({}, "View3"));
|
||||
}
|
||||
|
||||
TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:text">
|
||||
@@ -99,40 +100,43 @@ TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
|
||||
</aapt:attr>
|
||||
</View1>)EOF");
|
||||
|
||||
doc->file.name = test::parseNameOrDie("layout/main");
|
||||
doc->file.name = test::parseNameOrDie("layout/main");
|
||||
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
|
||||
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
EXPECT_EQ("View1", el->name);
|
||||
EXPECT_EQ("View1", el->name);
|
||||
|
||||
xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attrText);
|
||||
xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attrText);
|
||||
|
||||
xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable");
|
||||
ASSERT_NE(nullptr, attrDrawable);
|
||||
xml::Attribute* attrDrawable =
|
||||
el->findAttribute(xml::kSchemaAndroid, "drawable");
|
||||
ASSERT_NE(nullptr, attrDrawable);
|
||||
|
||||
// The two extracted resources should have different names.
|
||||
EXPECT_NE(attrText->value, attrDrawable->value);
|
||||
// The two extracted resources should have different names.
|
||||
EXPECT_NE(attrText->value, attrDrawable->value);
|
||||
|
||||
// The child <aapt:attr> elements should be gone.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
// The child <aapt:attr> elements should be gone.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
|
||||
xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDocText);
|
||||
el = xml::findRootElement(extractedDocText);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
xml::XmlResource* extractedDocText =
|
||||
parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDocText);
|
||||
el = xml::findRootElement(extractedDocText);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
|
||||
xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get();
|
||||
ASSERT_NE(nullptr, extractedDocDrawable);
|
||||
el = xml::findRootElement(extractedDocDrawable);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("vector", el->name);
|
||||
xml::XmlResource* extractedDocDrawable =
|
||||
parser.getExtractedInlineXmlDocuments()[1].get();
|
||||
ASSERT_NE(nullptr, extractedDocDrawable);
|
||||
el = xml::findRootElement(extractedDocDrawable);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("vector", el->name);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,8 @@ namespace aapt {
|
||||
|
||||
// Pixels are in RGBA_8888 packing.
|
||||
|
||||
#define RED "\xff\x00\x00\xff"
|
||||
#define BLUE "\x00\x00\xff\xff"
|
||||
#define RED "\xff\x00\x00\xff"
|
||||
#define BLUE "\x00\x00\xff\xff"
|
||||
#define GREEN "\xff\x00\x00\xff"
|
||||
#define GR_70 "\xff\x00\x00\xb3"
|
||||
#define GR_50 "\xff\x00\x00\x80"
|
||||
@@ -32,327 +32,346 @@ namespace aapt {
|
||||
#define TRANS "\x00\x00\x00\x00"
|
||||
|
||||
static uint8_t* k2x2[] = {
|
||||
(uint8_t*) WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kMixedNeutralColor3x3[] = {
|
||||
(uint8_t*) WHITE BLACK TRANS,
|
||||
(uint8_t*) TRANS RED TRANS,
|
||||
(uint8_t*) WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
|
||||
(uint8_t*)WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kTransparentNeutralColor3x3[] = {
|
||||
(uint8_t*) TRANS BLACK TRANS,
|
||||
(uint8_t*) BLACK RED BLACK,
|
||||
(uint8_t*) TRANS BLACK TRANS,
|
||||
(uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
|
||||
(uint8_t*)TRANS BLACK TRANS,
|
||||
};
|
||||
|
||||
static uint8_t* kSingleStretch7x6[] = {
|
||||
(uint8_t*) WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
|
||||
(uint8_t*) WHITE RED RED RED RED RED WHITE,
|
||||
(uint8_t*) BLACK RED RED RED RED RED WHITE,
|
||||
(uint8_t*) BLACK RED RED RED RED RED WHITE,
|
||||
(uint8_t*) WHITE RED RED RED RED RED WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
|
||||
(uint8_t*)WHITE RED RED RED RED RED WHITE,
|
||||
(uint8_t*)BLACK RED RED RED RED RED WHITE,
|
||||
(uint8_t*)BLACK RED RED RED RED RED WHITE,
|
||||
(uint8_t*)WHITE RED RED RED RED RED WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kMultipleStretch10x7[] = {
|
||||
(uint8_t*) WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
|
||||
(uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*) WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
|
||||
(uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kPadding6x5[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE BLACK,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE BLACK BLACK WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
|
||||
(uint8_t*) WHITE RED WHITE,
|
||||
(uint8_t*) RED WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE RED WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE RED WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kLayoutBounds5x5[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE RED WHITE RED WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE RED WHITE RED WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kAsymmetricLayoutBounds5x5[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE RED WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE RED WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kPaddingAndLayoutBounds5x5[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE BLACK,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*) WHITE RED BLACK RED WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE BLACK,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE RED,
|
||||
(uint8_t*)WHITE RED BLACK RED WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kColorfulImage5x5[] = {
|
||||
(uint8_t*) WHITE BLACK WHITE BLACK WHITE,
|
||||
(uint8_t*) BLACK RED BLUE GREEN WHITE,
|
||||
(uint8_t*) BLACK RED GREEN GREEN WHITE,
|
||||
(uint8_t*) WHITE TRANS BLUE GREEN WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE BLACK WHITE BLACK WHITE,
|
||||
(uint8_t*)BLACK RED BLUE GREEN WHITE,
|
||||
(uint8_t*)BLACK RED GREEN GREEN WHITE,
|
||||
(uint8_t*)WHITE TRANS BLUE GREEN WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kOutlineOpaque10x10[] = {
|
||||
(uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kOutlineTranslucent10x10[] = {
|
||||
(uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kOutlineOffsetTranslucent12x10[] = {
|
||||
(uint8_t*) WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)
|
||||
WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
|
||||
(uint8_t*)
|
||||
WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kOutlineRadius5x5[] = {
|
||||
(uint8_t*) WHITE BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*) BLACK TRANS GREEN TRANS WHITE,
|
||||
(uint8_t*) BLACK GREEN GREEN GREEN WHITE,
|
||||
(uint8_t*) BLACK TRANS GREEN TRANS WHITE,
|
||||
(uint8_t*) WHITE WHITE WHITE WHITE WHITE,
|
||||
(uint8_t*)WHITE BLACK BLACK BLACK WHITE,
|
||||
(uint8_t*)BLACK TRANS GREEN TRANS WHITE,
|
||||
(uint8_t*)BLACK GREEN GREEN GREEN WHITE,
|
||||
(uint8_t*)BLACK TRANS GREEN TRANS WHITE,
|
||||
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
|
||||
};
|
||||
|
||||
static uint8_t* kStretchAndPadding5x5[] = {
|
||||
(uint8_t*) WHITE WHITE BLACK WHITE WHITE,
|
||||
(uint8_t*) WHITE RED RED RED WHITE,
|
||||
(uint8_t*) BLACK RED RED RED BLACK,
|
||||
(uint8_t*) WHITE RED RED RED WHITE,
|
||||
(uint8_t*) WHITE WHITE BLACK WHITE WHITE,
|
||||
(uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
|
||||
(uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE,
|
||||
(uint8_t*)WHITE WHITE BLACK WHITE WHITE,
|
||||
};
|
||||
|
||||
TEST(NinePatchTest, Minimum3x3) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, MixedNeutralColors) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, TransparentNeutralColor) {
|
||||
std::string err;
|
||||
EXPECT_NE(nullptr, NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
|
||||
std::string err;
|
||||
EXPECT_NE(nullptr,
|
||||
NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, SingleStretchRegion) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kSingleStretch7x6, 7, 6, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kSingleStretch7x6, 7, 6, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
|
||||
ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
|
||||
ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
|
||||
|
||||
EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
|
||||
EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
|
||||
EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
|
||||
EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, MultipleStretchRegions) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
|
||||
ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
|
||||
ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
|
||||
|
||||
EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
|
||||
EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
|
||||
EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
|
||||
EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
|
||||
EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
|
||||
EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
|
||||
|
||||
EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
|
||||
EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
|
||||
EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
|
||||
EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, InferPaddingFromStretchRegions) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, Padding) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPadding6x5, 6, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kPadding6x5, 6, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr,
|
||||
NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, LayoutBounds) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
|
||||
|
||||
ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
|
||||
ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, PaddingAndLayoutBounds) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5,
|
||||
&err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, RegionColorsAreCorrect) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kColorfulImage5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kColorfulImage5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
|
||||
std::vector<uint32_t> expectedColors = {
|
||||
NinePatch::packRGBA((uint8_t*) RED),
|
||||
(uint32_t) android::Res_png_9patch::NO_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*) GREEN),
|
||||
(uint32_t) android::Res_png_9patch::TRANSPARENT_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*) BLUE),
|
||||
NinePatch::packRGBA((uint8_t*) GREEN),
|
||||
};
|
||||
EXPECT_EQ(expectedColors, ninePatch->regionColors);
|
||||
std::vector<uint32_t> expectedColors = {
|
||||
NinePatch::packRGBA((uint8_t*)RED),
|
||||
(uint32_t)android::Res_png_9patch::NO_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*)GREEN),
|
||||
(uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*)BLUE),
|
||||
NinePatch::packRGBA((uint8_t*)GREEN),
|
||||
};
|
||||
EXPECT_EQ(expectedColors, ninePatch->regionColors);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, OutlineFromOpaqueImage) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
|
||||
EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
|
||||
EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, OutlineFromTranslucentImage) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineTranslucent10x10, 10, 10,
|
||||
&err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
|
||||
EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kOutlineTranslucent10x10, 10, 10, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
|
||||
EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, OutlineFromOffCenterImage) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10,
|
||||
&err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
|
||||
// TODO(adamlesinski): The old AAPT algorithm searches from the outside to the middle
|
||||
// for each inset. If the outline is shifted, the search may not find a closer bounds.
|
||||
// This check should be:
|
||||
// EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
|
||||
// but until I know what behaviour I'm breaking, I will leave it at the incorrect:
|
||||
EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
|
||||
// TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
|
||||
// middle
|
||||
// for each inset. If the outline is shifted, the search may not find a closer
|
||||
// bounds.
|
||||
// This check should be:
|
||||
// EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
|
||||
// but until I know what behaviour I'm breaking, I will leave it at the
|
||||
// incorrect:
|
||||
EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
|
||||
|
||||
EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, OutlineRadius) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
|
||||
EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
|
||||
EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
|
||||
}
|
||||
|
||||
::testing::AssertionResult bigEndianOne(uint8_t* cursor) {
|
||||
if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
return ::testing::AssertionFailure() << "Not BigEndian 1";
|
||||
if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
return ::testing::AssertionFailure() << "Not BigEndian 1";
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, SerializePngEndianness) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
|
||||
size_t len;
|
||||
std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
|
||||
ASSERT_NE(nullptr, data);
|
||||
ASSERT_NE(0u, len);
|
||||
size_t len;
|
||||
std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
|
||||
ASSERT_NE(nullptr, data);
|
||||
ASSERT_NE(0u, len);
|
||||
|
||||
// Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + yDivsOffset
|
||||
// (12 bytes)
|
||||
uint8_t* cursor = data.get() + 12;
|
||||
// Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
|
||||
// yDivsOffset
|
||||
// (12 bytes)
|
||||
uint8_t* cursor = data.get() + 12;
|
||||
|
||||
// Check that padding is big-endian. Expecting value 1.
|
||||
EXPECT_TRUE(bigEndianOne(cursor));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 4));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 8));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 12));
|
||||
// Check that padding is big-endian. Expecting value 1.
|
||||
EXPECT_TRUE(bigEndianOne(cursor));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 4));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 8));
|
||||
EXPECT_TRUE(bigEndianOne(cursor + 12));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,51 +31,48 @@
|
||||
namespace aapt {
|
||||
|
||||
struct PngOptions {
|
||||
int grayScaleTolerance = 0;
|
||||
int grayScaleTolerance = 0;
|
||||
};
|
||||
|
||||
class Png {
|
||||
public:
|
||||
explicit Png(IDiagnostics* diag) : mDiag(diag) {
|
||||
}
|
||||
public:
|
||||
explicit Png(IDiagnostics* diag) : mDiag(diag) {}
|
||||
|
||||
bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
|
||||
const PngOptions& options);
|
||||
bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
|
||||
const PngOptions& options);
|
||||
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Png);
|
||||
DISALLOW_COPY_AND_ASSIGN(Png);
|
||||
};
|
||||
|
||||
/**
|
||||
* An InputStream that filters out unimportant PNG chunks.
|
||||
*/
|
||||
class PngChunkFilter : public io::InputStream {
|
||||
public:
|
||||
explicit PngChunkFilter(const StringPiece& data);
|
||||
public:
|
||||
explicit PngChunkFilter(const StringPiece& data);
|
||||
|
||||
bool Next(const void** buffer, int* len) override;
|
||||
void BackUp(int count) override;
|
||||
bool Skip(int count) override;
|
||||
bool Next(const void** buffer, int* len) override;
|
||||
void BackUp(int count) override;
|
||||
bool Skip(int count) override;
|
||||
|
||||
int64_t ByteCount() const override {
|
||||
return static_cast<int64_t>(mWindowStart);
|
||||
}
|
||||
int64_t ByteCount() const override {
|
||||
return static_cast<int64_t>(mWindowStart);
|
||||
}
|
||||
|
||||
bool HadError() const override {
|
||||
return mError;
|
||||
}
|
||||
bool HadError() const override { return mError; }
|
||||
|
||||
private:
|
||||
bool consumeWindow(const void** buffer, int* len);
|
||||
private:
|
||||
bool consumeWindow(const void** buffer, int* len);
|
||||
|
||||
StringPiece mData;
|
||||
size_t mWindowStart = 0;
|
||||
size_t mWindowEnd = 0;
|
||||
bool mError = false;
|
||||
StringPiece mData;
|
||||
size_t mWindowStart = 0;
|
||||
size_t mWindowEnd = 0;
|
||||
bool mError = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
|
||||
DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,11 +81,13 @@ private:
|
||||
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in);
|
||||
|
||||
/**
|
||||
* Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream as a PNG.
|
||||
* Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
|
||||
* as a PNG.
|
||||
*/
|
||||
bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch,
|
||||
io::OutputStream* out, const PngOptions& options);
|
||||
bool writePng(IAaptContext* context, const Image* image,
|
||||
const NinePatch* ninePatch, io::OutputStream* out,
|
||||
const PngOptions& options);
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_PNG_H
|
||||
#endif // AAPT_PNG_H
|
||||
|
||||
@@ -25,149 +25,149 @@ static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
|
||||
// Useful helper function that encodes individual bytes into a uint32
|
||||
// at compile time.
|
||||
constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
|
||||
return (((uint32_t) a) << 24)
|
||||
| (((uint32_t) b) << 16)
|
||||
| (((uint32_t) c) << 8)
|
||||
| ((uint32_t) d);
|
||||
return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
|
||||
((uint32_t)d);
|
||||
}
|
||||
|
||||
// Whitelist of PNG chunk types that we want to keep in the resulting PNG.
|
||||
enum PngChunkTypes {
|
||||
kPngChunkIHDR = u32(73, 72, 68, 82),
|
||||
kPngChunkIDAT = u32(73, 68, 65, 84),
|
||||
kPngChunkIEND = u32(73, 69, 78, 68),
|
||||
kPngChunkPLTE = u32(80, 76, 84, 69),
|
||||
kPngChunktRNS = u32(116, 82, 78, 83),
|
||||
kPngChunksRGB = u32(115, 82, 71, 66),
|
||||
kPngChunkIHDR = u32(73, 72, 68, 82),
|
||||
kPngChunkIDAT = u32(73, 68, 65, 84),
|
||||
kPngChunkIEND = u32(73, 69, 78, 68),
|
||||
kPngChunkPLTE = u32(80, 76, 84, 69),
|
||||
kPngChunktRNS = u32(116, 82, 78, 83),
|
||||
kPngChunksRGB = u32(115, 82, 71, 66),
|
||||
};
|
||||
|
||||
static uint32_t peek32LE(const char* data) {
|
||||
uint32_t word = ((uint32_t) data[0]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t) data[1]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t) data[2]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t) data[3]) & 0x000000ff;
|
||||
return word;
|
||||
uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t)data[1]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t)data[2]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t)data[3]) & 0x000000ff;
|
||||
return word;
|
||||
}
|
||||
|
||||
static bool isPngChunkWhitelisted(uint32_t type) {
|
||||
switch (type) {
|
||||
switch (type) {
|
||||
case kPngChunkIHDR:
|
||||
case kPngChunkIDAT:
|
||||
case kPngChunkIEND:
|
||||
case kPngChunkPLTE:
|
||||
case kPngChunktRNS:
|
||||
case kPngChunksRGB:
|
||||
return true;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PngChunkFilter::PngChunkFilter(const StringPiece& data) : mData(data) {
|
||||
if (util::stringStartsWith(mData, kPngSignature)) {
|
||||
mWindowStart = 0;
|
||||
mWindowEnd = strlen(kPngSignature);
|
||||
} else {
|
||||
mError = true;
|
||||
}
|
||||
if (util::stringStartsWith(mData, kPngSignature)) {
|
||||
mWindowStart = 0;
|
||||
mWindowEnd = strlen(kPngSignature);
|
||||
} else {
|
||||
mError = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PngChunkFilter::consumeWindow(const void** buffer, int* len) {
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
// We have bytes to give from our window.
|
||||
const int bytesRead = (int) (mWindowEnd - mWindowStart);
|
||||
*buffer = mData.data() + mWindowStart;
|
||||
*len = bytesRead;
|
||||
mWindowStart = mWindowEnd;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
// We have bytes to give from our window.
|
||||
const int bytesRead = (int)(mWindowEnd - mWindowStart);
|
||||
*buffer = mData.data() + mWindowStart;
|
||||
*len = bytesRead;
|
||||
mWindowStart = mWindowEnd;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PngChunkFilter::Next(const void** buffer, int* len) {
|
||||
if (mError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In case BackUp was called, we must consume the window.
|
||||
if (consumeWindow(buffer, len)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advance the window as far as possible (until we meet a chunk that
|
||||
// we want to strip).
|
||||
while (mWindowEnd < mData.size()) {
|
||||
// Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
|
||||
const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
|
||||
|
||||
// Is there enough room for a chunk header?
|
||||
if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
|
||||
mError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the chunk length.
|
||||
const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
|
||||
if (((uint64_t) chunkLen) + ((uint64_t) mWindowEnd) + sizeof(uint32_t) > mData.size()) {
|
||||
// Overflow.
|
||||
mError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we strip this chunk?
|
||||
const uint32_t chunkType = peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
|
||||
if (isPngChunkWhitelisted(chunkType)) {
|
||||
// Advance the window to include this chunk.
|
||||
mWindowEnd += kMinChunkHeaderSize + chunkLen;
|
||||
} else {
|
||||
// We want to strip this chunk. If we accumulated a window,
|
||||
// we must return the window now.
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The window is empty, so we can advance past this chunk
|
||||
// and keep looking for the next good chunk,
|
||||
mWindowEnd += kMinChunkHeaderSize + chunkLen;
|
||||
mWindowStart = mWindowEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if (consumeWindow(buffer, len)) {
|
||||
return true;
|
||||
}
|
||||
if (mError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In case BackUp was called, we must consume the window.
|
||||
if (consumeWindow(buffer, len)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advance the window as far as possible (until we meet a chunk that
|
||||
// we want to strip).
|
||||
while (mWindowEnd < mData.size()) {
|
||||
// Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
|
||||
const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
|
||||
|
||||
// Is there enough room for a chunk header?
|
||||
if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
|
||||
mError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the chunk length.
|
||||
const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
|
||||
if (((uint64_t)chunkLen) + ((uint64_t)mWindowEnd) + sizeof(uint32_t) >
|
||||
mData.size()) {
|
||||
// Overflow.
|
||||
mError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we strip this chunk?
|
||||
const uint32_t chunkType =
|
||||
peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
|
||||
if (isPngChunkWhitelisted(chunkType)) {
|
||||
// Advance the window to include this chunk.
|
||||
mWindowEnd += kMinChunkHeaderSize + chunkLen;
|
||||
} else {
|
||||
// We want to strip this chunk. If we accumulated a window,
|
||||
// we must return the window now.
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The window is empty, so we can advance past this chunk
|
||||
// and keep looking for the next good chunk,
|
||||
mWindowEnd += kMinChunkHeaderSize + chunkLen;
|
||||
mWindowStart = mWindowEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if (consumeWindow(buffer, len)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PngChunkFilter::BackUp(int count) {
|
||||
if (mError) {
|
||||
return;
|
||||
}
|
||||
mWindowStart -= count;
|
||||
if (mError) {
|
||||
return;
|
||||
}
|
||||
mWindowStart -= count;
|
||||
}
|
||||
|
||||
bool PngChunkFilter::Skip(int count) {
|
||||
if (mError) {
|
||||
return false;
|
||||
}
|
||||
if (mError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const void* buffer;
|
||||
int len;
|
||||
while (count > 0) {
|
||||
if (!Next(&buffer, &len)) {
|
||||
return false;
|
||||
}
|
||||
if (len > count) {
|
||||
BackUp(len - count);
|
||||
count = 0;
|
||||
} else {
|
||||
count -= len;
|
||||
}
|
||||
const void* buffer;
|
||||
int len;
|
||||
while (count > 0) {
|
||||
if (!Next(&buffer, &len)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (len > count) {
|
||||
BackUp(len - count);
|
||||
count = 0;
|
||||
} else {
|
||||
count -= len;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,235 +14,242 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compile/PseudolocaleGenerator.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "compile/PseudolocaleGenerator.h"
|
||||
#include "compile/Pseudolocalizer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
|
||||
Pseudolocalizer::Method method,
|
||||
StringPool* pool) {
|
||||
Pseudolocalizer localizer(method);
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
|
||||
Pseudolocalizer localizer(method);
|
||||
|
||||
const StringPiece originalText = *string->value->str;
|
||||
const StringPiece originalText = *string->value->str;
|
||||
|
||||
StyleString localized;
|
||||
StyleString localized;
|
||||
|
||||
// Copy the spans. We will update their offsets when we localize.
|
||||
localized.spans.reserve(string->value->spans.size());
|
||||
for (const StringPool::Span& span : string->value->spans) {
|
||||
localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar });
|
||||
// Copy the spans. We will update their offsets when we localize.
|
||||
localized.spans.reserve(string->value->spans.size());
|
||||
for (const StringPool::Span& span : string->value->spans) {
|
||||
localized.spans.push_back(Span{*span.name, span.firstChar, span.lastChar});
|
||||
}
|
||||
|
||||
// The ranges are all represented with a single value. This is the start of
|
||||
// one range and
|
||||
// end of another.
|
||||
struct Range {
|
||||
size_t start;
|
||||
|
||||
// Once the new string is localized, these are the pointers to the spans to
|
||||
// adjust.
|
||||
// Since this struct represents the start of one range and end of another,
|
||||
// we have
|
||||
// the two pointers respectively.
|
||||
uint32_t* updateStart;
|
||||
uint32_t* updateEnd;
|
||||
};
|
||||
|
||||
auto cmp = [](const Range& r, size_t index) -> bool {
|
||||
return r.start < index;
|
||||
};
|
||||
|
||||
// Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
|
||||
// The ranges are the spaces in between. In this example, with a total string
|
||||
// length of 9,
|
||||
// the vector represents: (0,1], (2,4], (5,6], (7,9]
|
||||
//
|
||||
std::vector<Range> ranges;
|
||||
ranges.push_back(Range{0});
|
||||
ranges.push_back(Range{originalText.size() - 1});
|
||||
for (size_t i = 0; i < string->value->spans.size(); i++) {
|
||||
const StringPool::Span& span = string->value->spans[i];
|
||||
|
||||
// Insert or update the Range marker for the start of this span.
|
||||
auto iter =
|
||||
std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.firstChar) {
|
||||
iter->updateStart = &localized.spans[i].firstChar;
|
||||
} else {
|
||||
ranges.insert(
|
||||
iter, Range{span.firstChar, &localized.spans[i].firstChar, nullptr});
|
||||
}
|
||||
|
||||
// The ranges are all represented with a single value. This is the start of one range and
|
||||
// end of another.
|
||||
struct Range {
|
||||
size_t start;
|
||||
// Insert or update the Range marker for the end of this span.
|
||||
iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.lastChar) {
|
||||
iter->updateEnd = &localized.spans[i].lastChar;
|
||||
} else {
|
||||
ranges.insert(
|
||||
iter, Range{span.lastChar, nullptr, &localized.spans[i].lastChar});
|
||||
}
|
||||
}
|
||||
|
||||
// Once the new string is localized, these are the pointers to the spans to adjust.
|
||||
// Since this struct represents the start of one range and end of another, we have
|
||||
// the two pointers respectively.
|
||||
uint32_t* updateStart;
|
||||
uint32_t* updateEnd;
|
||||
};
|
||||
localized.str += localizer.start();
|
||||
|
||||
auto cmp = [](const Range& r, size_t index) -> bool {
|
||||
return r.start < index;
|
||||
};
|
||||
|
||||
// Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
|
||||
// The ranges are the spaces in between. In this example, with a total string length of 9,
|
||||
// the vector represents: (0,1], (2,4], (5,6], (7,9]
|
||||
//
|
||||
std::vector<Range> ranges;
|
||||
ranges.push_back(Range{ 0 });
|
||||
ranges.push_back(Range{ originalText.size() - 1 });
|
||||
for (size_t i = 0; i < string->value->spans.size(); i++) {
|
||||
const StringPool::Span& span = string->value->spans[i];
|
||||
|
||||
// Insert or update the Range marker for the start of this span.
|
||||
auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.firstChar) {
|
||||
iter->updateStart = &localized.spans[i].firstChar;
|
||||
} else {
|
||||
ranges.insert(iter,
|
||||
Range{ span.firstChar, &localized.spans[i].firstChar, nullptr });
|
||||
}
|
||||
|
||||
// Insert or update the Range marker for the end of this span.
|
||||
iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.lastChar) {
|
||||
iter->updateEnd = &localized.spans[i].lastChar;
|
||||
} else {
|
||||
ranges.insert(iter,
|
||||
Range{ span.lastChar, nullptr, &localized.spans[i].lastChar });
|
||||
}
|
||||
// Iterate over the ranges and localize each section.
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
const size_t start = ranges[i].start;
|
||||
size_t len = originalText.size() - start;
|
||||
if (i + 1 < ranges.size()) {
|
||||
len = ranges[i + 1].start - start;
|
||||
}
|
||||
|
||||
localized.str += localizer.start();
|
||||
|
||||
// Iterate over the ranges and localize each section.
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
const size_t start = ranges[i].start;
|
||||
size_t len = originalText.size() - start;
|
||||
if (i + 1 < ranges.size()) {
|
||||
len = ranges[i + 1].start - start;
|
||||
}
|
||||
|
||||
if (ranges[i].updateStart) {
|
||||
*ranges[i].updateStart = localized.str.size();
|
||||
}
|
||||
|
||||
if (ranges[i].updateEnd) {
|
||||
*ranges[i].updateEnd = localized.str.size();
|
||||
}
|
||||
|
||||
localized.str += localizer.text(originalText.substr(start, len));
|
||||
if (ranges[i].updateStart) {
|
||||
*ranges[i].updateStart = localized.str.size();
|
||||
}
|
||||
|
||||
localized.str += localizer.end();
|
||||
if (ranges[i].updateEnd) {
|
||||
*ranges[i].updateEnd = localized.str.size();
|
||||
}
|
||||
|
||||
std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>(
|
||||
pool->makeRef(localized));
|
||||
localizedString->setSource(string->getSource());
|
||||
return localizedString;
|
||||
localized.str += localizer.text(originalText.substr(start, len));
|
||||
}
|
||||
|
||||
localized.str += localizer.end();
|
||||
|
||||
std::unique_ptr<StyledString> localizedString =
|
||||
util::make_unique<StyledString>(pool->makeRef(localized));
|
||||
localizedString->setSource(string->getSource());
|
||||
return localizedString;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Visitor : public RawValueVisitor {
|
||||
StringPool* mPool;
|
||||
Pseudolocalizer::Method mMethod;
|
||||
Pseudolocalizer mLocalizer;
|
||||
StringPool* mPool;
|
||||
Pseudolocalizer::Method mMethod;
|
||||
Pseudolocalizer mLocalizer;
|
||||
|
||||
// Either value or item will be populated upon visiting the value.
|
||||
std::unique_ptr<Value> mValue;
|
||||
std::unique_ptr<Item> mItem;
|
||||
// Either value or item will be populated upon visiting the value.
|
||||
std::unique_ptr<Value> mValue;
|
||||
std::unique_ptr<Item> mItem;
|
||||
|
||||
Visitor(StringPool* pool, Pseudolocalizer::Method method) :
|
||||
mPool(pool), mMethod(method), mLocalizer(method) {
|
||||
}
|
||||
Visitor(StringPool* pool, Pseudolocalizer::Method method)
|
||||
: mPool(pool), mMethod(method), mLocalizer(method) {}
|
||||
|
||||
void visit(Plural* plural) override {
|
||||
std::unique_ptr<Plural> localized = util::make_unique<Plural>();
|
||||
for (size_t i = 0; i < plural->values.size(); i++) {
|
||||
Visitor subVisitor(mPool, mMethod);
|
||||
if (plural->values[i]) {
|
||||
plural->values[i]->accept(&subVisitor);
|
||||
if (subVisitor.mValue) {
|
||||
localized->values[i] = std::move(subVisitor.mItem);
|
||||
} else {
|
||||
localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool));
|
||||
}
|
||||
}
|
||||
void visit(Plural* plural) override {
|
||||
std::unique_ptr<Plural> localized = util::make_unique<Plural>();
|
||||
for (size_t i = 0; i < plural->values.size(); i++) {
|
||||
Visitor subVisitor(mPool, mMethod);
|
||||
if (plural->values[i]) {
|
||||
plural->values[i]->accept(&subVisitor);
|
||||
if (subVisitor.mValue) {
|
||||
localized->values[i] = std::move(subVisitor.mItem);
|
||||
} else {
|
||||
localized->values[i] =
|
||||
std::unique_ptr<Item>(plural->values[i]->clone(mPool));
|
||||
}
|
||||
localized->setSource(plural->getSource());
|
||||
localized->setWeak(true);
|
||||
mValue = std::move(localized);
|
||||
}
|
||||
}
|
||||
localized->setSource(plural->getSource());
|
||||
localized->setWeak(true);
|
||||
mValue = std::move(localized);
|
||||
}
|
||||
|
||||
void visit(String* string) override {
|
||||
std::string result = mLocalizer.start() + mLocalizer.text(*string->value) +
|
||||
mLocalizer.end();
|
||||
std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
|
||||
localized->setSource(string->getSource());
|
||||
localized->setWeak(true);
|
||||
mItem = std::move(localized);
|
||||
}
|
||||
void visit(String* string) override {
|
||||
std::string result =
|
||||
mLocalizer.start() + mLocalizer.text(*string->value) + mLocalizer.end();
|
||||
std::unique_ptr<String> localized =
|
||||
util::make_unique<String>(mPool->makeRef(result));
|
||||
localized->setSource(string->getSource());
|
||||
localized->setWeak(true);
|
||||
mItem = std::move(localized);
|
||||
}
|
||||
|
||||
void visit(StyledString* string) override {
|
||||
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
|
||||
mItem->setWeak(true);
|
||||
}
|
||||
void visit(StyledString* string) override {
|
||||
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
|
||||
mItem->setWeak(true);
|
||||
}
|
||||
};
|
||||
|
||||
ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
|
||||
Pseudolocalizer::Method m) {
|
||||
ConfigDescription modified = base;
|
||||
switch (m) {
|
||||
ConfigDescription modified = base;
|
||||
switch (m) {
|
||||
case Pseudolocalizer::Method::kAccent:
|
||||
modified.language[0] = 'e';
|
||||
modified.language[1] = 'n';
|
||||
modified.country[0] = 'X';
|
||||
modified.country[1] = 'A';
|
||||
break;
|
||||
modified.language[0] = 'e';
|
||||
modified.language[1] = 'n';
|
||||
modified.country[0] = 'X';
|
||||
modified.country[1] = 'A';
|
||||
break;
|
||||
|
||||
case Pseudolocalizer::Method::kBidi:
|
||||
modified.language[0] = 'a';
|
||||
modified.language[1] = 'r';
|
||||
modified.country[0] = 'X';
|
||||
modified.country[1] = 'B';
|
||||
break;
|
||||
modified.language[0] = 'a';
|
||||
modified.language[1] = 'r';
|
||||
modified.country[0] = 'X';
|
||||
modified.country[1] = 'B';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return modified;
|
||||
break;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
|
||||
ResourceConfigValue* originalValue,
|
||||
StringPool* pool,
|
||||
ResourceEntry* entry) {
|
||||
Visitor visitor(pool, method);
|
||||
originalValue->value->accept(&visitor);
|
||||
StringPool* pool, ResourceEntry* entry) {
|
||||
Visitor visitor(pool, method);
|
||||
originalValue->value->accept(&visitor);
|
||||
|
||||
std::unique_ptr<Value> localizedValue;
|
||||
if (visitor.mValue) {
|
||||
localizedValue = std::move(visitor.mValue);
|
||||
} else if (visitor.mItem) {
|
||||
localizedValue = std::move(visitor.mItem);
|
||||
}
|
||||
std::unique_ptr<Value> localizedValue;
|
||||
if (visitor.mValue) {
|
||||
localizedValue = std::move(visitor.mValue);
|
||||
} else if (visitor.mItem) {
|
||||
localizedValue = std::move(visitor.mItem);
|
||||
}
|
||||
|
||||
if (!localizedValue) {
|
||||
return;
|
||||
}
|
||||
if (!localizedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
|
||||
originalValue->config, method);
|
||||
ConfigDescription configWithAccent =
|
||||
modifyConfigForPseudoLocale(originalValue->config, method);
|
||||
|
||||
ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
|
||||
configWithAccent, originalValue->product);
|
||||
if (!newConfigValue->value) {
|
||||
// Only use auto-generated pseudo-localization if none is defined.
|
||||
newConfigValue->value = std::move(localizedValue);
|
||||
}
|
||||
ResourceConfigValue* newConfigValue =
|
||||
entry->findOrCreateValue(configWithAccent, originalValue->product);
|
||||
if (!newConfigValue->value) {
|
||||
// Only use auto-generated pseudo-localization if none is defined.
|
||||
newConfigValue->value = std::move(localizedValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value is pseudolocalizable if it does not define a locale (or is the default locale)
|
||||
* A value is pseudolocalizable if it does not define a locale (or is the
|
||||
* default locale)
|
||||
* and is translateable.
|
||||
*/
|
||||
static bool isPseudolocalizable(ResourceConfigValue* configValue) {
|
||||
const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
|
||||
if (diff & ConfigDescription::CONFIG_LOCALE) {
|
||||
return false;
|
||||
}
|
||||
return configValue->value->isTranslateable();
|
||||
const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
|
||||
if (diff & ConfigDescription::CONFIG_LOCALE) {
|
||||
return false;
|
||||
}
|
||||
return configValue->value->isTranslateable();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
|
||||
bool PseudolocaleGenerator::consume(IAaptContext* context,
|
||||
ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
std::vector<ResourceConfigValue*> values =
|
||||
entry->findValuesIf(isPseudolocalizable);
|
||||
|
||||
for (ResourceConfigValue* value : values) {
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
|
||||
&table->stringPool, entry.get());
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
|
||||
&table->stringPool, entry.get());
|
||||
}
|
||||
}
|
||||
for (ResourceConfigValue* value : values) {
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
|
||||
&table->stringPool, entry.get());
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
|
||||
&table->stringPool, entry.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -23,14 +23,13 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
|
||||
Pseudolocalizer::Method method,
|
||||
StringPool* pool);
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
StyledString* string, Pseudolocalizer::Method method, StringPool* pool);
|
||||
|
||||
struct PseudolocaleGenerator : public IResourceTableConsumer {
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */
|
||||
|
||||
@@ -23,99 +23,110 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) {
|
||||
StringPool pool;
|
||||
StyleString originalStyle;
|
||||
originalStyle.str = "Hello world!";
|
||||
originalStyle.spans = { Span{ "b", 2, 3 }, Span{ "b", 6, 7 }, Span{ "i", 1, 10 } };
|
||||
StringPool pool;
|
||||
StyleString originalStyle;
|
||||
originalStyle.str = "Hello world!";
|
||||
originalStyle.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
|
||||
|
||||
std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
|
||||
Pseudolocalizer::Method::kNone, &pool);
|
||||
std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
|
||||
Pseudolocalizer::Method::kNone, &pool);
|
||||
|
||||
EXPECT_EQ(originalStyle.str, *newString->value->str);
|
||||
ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
|
||||
EXPECT_EQ(originalStyle.str, *newString->value->str);
|
||||
ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
|
||||
|
||||
EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
|
||||
EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
|
||||
EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
|
||||
EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
|
||||
EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
|
||||
EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
|
||||
|
||||
EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
|
||||
EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
|
||||
EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
|
||||
EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
|
||||
EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
|
||||
EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
|
||||
|
||||
EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("Hello worl").size(), newString->value->spans[2].lastChar);
|
||||
EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
|
||||
EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("Hello worl").size(),
|
||||
newString->value->spans[2].lastChar);
|
||||
EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
|
||||
|
||||
originalStyle.spans.push_back(Span{ "em", 0, 11u });
|
||||
originalStyle.spans.push_back(Span{"em", 0, 11u});
|
||||
|
||||
newString = pseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
|
||||
Pseudolocalizer::Method::kAccent, &pool);
|
||||
newString = pseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
|
||||
Pseudolocalizer::Method::kAccent, &pool);
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
|
||||
ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
|
||||
ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
|
||||
EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ").size(), newString->value->spans[1].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), newString->value->spans[1].lastChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ").size(),
|
||||
newString->value->spans[1].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
|
||||
newString->value->spans[1].lastChar);
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), newString->value->spans[2].lastChar);
|
||||
EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
|
||||
newString->value->spans[2].lastChar);
|
||||
|
||||
EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), newString->value->spans[3].lastChar);
|
||||
EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
|
||||
newString->value->spans[3].lastChar);
|
||||
}
|
||||
|
||||
TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addString("android:string/one", "one")
|
||||
.addString("android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two")
|
||||
.addString("android:string/three", "three")
|
||||
.addString("android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
|
||||
"three")
|
||||
.addString("android:string/four", "four")
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.addString("android:string/one", "one")
|
||||
.addString("android:string/two", ResourceId{},
|
||||
test::parseConfigOrDie("en"), "two")
|
||||
.addString("android:string/three", "three")
|
||||
.addString("android:string/three", ResourceId{},
|
||||
test::parseConfigOrDie("en-rXA"), "three")
|
||||
.addString("android:string/four", "four")
|
||||
.build();
|
||||
|
||||
String* val = test::getValue<String>(table.get(), "android:string/four");
|
||||
ASSERT_NE(nullptr, val);
|
||||
val->setTranslateable(false);
|
||||
String* val = test::getValue<String>(table.get(), "android:string/four");
|
||||
ASSERT_NE(nullptr, val);
|
||||
val->setTranslateable(false);
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
PseudolocaleGenerator generator;
|
||||
ASSERT_TRUE(generator.consume(context.get(), table.get()));
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
PseudolocaleGenerator generator;
|
||||
ASSERT_TRUE(generator.consume(context.get(), table.get()));
|
||||
|
||||
// Normal pseudolocalization should take place.
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
// Normal pseudolocalization should take place.
|
||||
ASSERT_NE(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/one",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_NE(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/one",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
|
||||
// No default config for android:string/two, so no pseudlocales should exist.
|
||||
ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
// No default config for android:string/two, so no pseudlocales should exist.
|
||||
ASSERT_EQ(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/two",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_EQ(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/two",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
|
||||
// Check that we didn't override manual pseudolocalization.
|
||||
val = test::getValueForConfig<String>(table.get(), "android:string/three",
|
||||
test::parseConfigOrDie("en-rXA"));
|
||||
ASSERT_NE(nullptr, val);
|
||||
EXPECT_EQ(std::string("three"), *val->value);
|
||||
|
||||
// Check that we didn't override manual pseudolocalization.
|
||||
val = test::getValueForConfig<String>(table.get(), "android:string/three",
|
||||
test::parseConfigOrDie("en-rXA"));
|
||||
ASSERT_NE(nullptr, val);
|
||||
EXPECT_EQ(std::string("three"), *val->value);
|
||||
|
||||
ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/three",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
|
||||
// Check that four's translateable marker was honored.
|
||||
ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
ASSERT_NE(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/three",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
|
||||
// Check that four's translateable marker was honored.
|
||||
ASSERT_EQ(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/four",
|
||||
test::parseConfigOrDie("en-rXA")));
|
||||
ASSERT_EQ(nullptr,
|
||||
test::getValueForConfig<String>(table.get(), "android:string/four",
|
||||
test::parseConfigOrDie("ar-rXB")));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
namespace aapt {
|
||||
|
||||
// String basis to generate expansion
|
||||
static const std::string k_expansion_string = "one two three "
|
||||
"four five six seven eight nine ten eleven twelve thirteen "
|
||||
"fourteen fiveteen sixteen seventeen nineteen twenty";
|
||||
static const std::string k_expansion_string =
|
||||
"one two three "
|
||||
"four five six seven eight nine ten eleven twelve thirteen "
|
||||
"fourteen fiveteen sixteen seventeen nineteen twenty";
|
||||
|
||||
// Special unicode characters to override directionality of the words
|
||||
static const std::string k_rlm = "\u200f";
|
||||
@@ -37,229 +38,310 @@ static const char k_arg_start = '{';
|
||||
static const char k_arg_end = '}';
|
||||
|
||||
class PseudoMethodNone : public PseudoMethodImpl {
|
||||
public:
|
||||
std::string text(const StringPiece& text) override { return text.toString(); }
|
||||
std::string placeholder(const StringPiece& text) override { return text.toString(); }
|
||||
public:
|
||||
std::string text(const StringPiece& text) override { return text.toString(); }
|
||||
std::string placeholder(const StringPiece& text) override {
|
||||
return text.toString();
|
||||
}
|
||||
};
|
||||
|
||||
class PseudoMethodBidi : public PseudoMethodImpl {
|
||||
public:
|
||||
std::string text(const StringPiece& text) override;
|
||||
std::string placeholder(const StringPiece& text) override;
|
||||
public:
|
||||
std::string text(const StringPiece& text) override;
|
||||
std::string placeholder(const StringPiece& text) override;
|
||||
};
|
||||
|
||||
class PseudoMethodAccent : public PseudoMethodImpl {
|
||||
public:
|
||||
PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
|
||||
std::string start() override;
|
||||
std::string end() override;
|
||||
std::string text(const StringPiece& text) override;
|
||||
std::string placeholder(const StringPiece& text) override;
|
||||
private:
|
||||
size_t mDepth;
|
||||
size_t mWordCount;
|
||||
size_t mLength;
|
||||
public:
|
||||
PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
|
||||
std::string start() override;
|
||||
std::string end() override;
|
||||
std::string text(const StringPiece& text) override;
|
||||
std::string placeholder(const StringPiece& text) override;
|
||||
|
||||
private:
|
||||
size_t mDepth;
|
||||
size_t mWordCount;
|
||||
size_t mLength;
|
||||
};
|
||||
|
||||
Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
|
||||
setMethod(method);
|
||||
setMethod(method);
|
||||
}
|
||||
|
||||
void Pseudolocalizer::setMethod(Method method) {
|
||||
switch (method) {
|
||||
switch (method) {
|
||||
case Method::kNone:
|
||||
mImpl = util::make_unique<PseudoMethodNone>();
|
||||
break;
|
||||
mImpl = util::make_unique<PseudoMethodNone>();
|
||||
break;
|
||||
case Method::kAccent:
|
||||
mImpl = util::make_unique<PseudoMethodAccent>();
|
||||
break;
|
||||
mImpl = util::make_unique<PseudoMethodAccent>();
|
||||
break;
|
||||
case Method::kBidi:
|
||||
mImpl = util::make_unique<PseudoMethodBidi>();
|
||||
break;
|
||||
}
|
||||
mImpl = util::make_unique<PseudoMethodBidi>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Pseudolocalizer::text(const StringPiece& text) {
|
||||
std::string out;
|
||||
size_t depth = mLastDepth;
|
||||
size_t lastpos, pos;
|
||||
const size_t length = text.size();
|
||||
const char* str = text.data();
|
||||
bool escaped = false;
|
||||
for (lastpos = pos = 0; pos < length; pos++) {
|
||||
char16_t c = str[pos];
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\'') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == k_arg_start) {
|
||||
depth++;
|
||||
} else if (c == k_arg_end && depth) {
|
||||
depth--;
|
||||
}
|
||||
|
||||
if (mLastDepth != depth || pos == length - 1) {
|
||||
bool pseudo = ((mLastDepth % 2) == 0);
|
||||
size_t nextpos = pos;
|
||||
if (!pseudo || depth == mLastDepth) {
|
||||
nextpos++;
|
||||
}
|
||||
size_t size = nextpos - lastpos;
|
||||
if (size) {
|
||||
std::string chunk = text.substr(lastpos, size).toString();
|
||||
if (pseudo) {
|
||||
chunk = mImpl->text(chunk);
|
||||
} else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) {
|
||||
chunk = mImpl->placeholder(chunk);
|
||||
}
|
||||
out.append(chunk);
|
||||
}
|
||||
if (pseudo && depth < mLastDepth) { // End of message
|
||||
out.append(mImpl->end());
|
||||
} else if (!pseudo && depth > mLastDepth) { // Start of message
|
||||
out.append(mImpl->start());
|
||||
}
|
||||
lastpos = nextpos;
|
||||
mLastDepth = depth;
|
||||
}
|
||||
std::string out;
|
||||
size_t depth = mLastDepth;
|
||||
size_t lastpos, pos;
|
||||
const size_t length = text.size();
|
||||
const char* str = text.data();
|
||||
bool escaped = false;
|
||||
for (lastpos = pos = 0; pos < length; pos++) {
|
||||
char16_t c = str[pos];
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
return out;
|
||||
if (c == '\'') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == k_arg_start) {
|
||||
depth++;
|
||||
} else if (c == k_arg_end && depth) {
|
||||
depth--;
|
||||
}
|
||||
|
||||
if (mLastDepth != depth || pos == length - 1) {
|
||||
bool pseudo = ((mLastDepth % 2) == 0);
|
||||
size_t nextpos = pos;
|
||||
if (!pseudo || depth == mLastDepth) {
|
||||
nextpos++;
|
||||
}
|
||||
size_t size = nextpos - lastpos;
|
||||
if (size) {
|
||||
std::string chunk = text.substr(lastpos, size).toString();
|
||||
if (pseudo) {
|
||||
chunk = mImpl->text(chunk);
|
||||
} else if (str[lastpos] == k_arg_start &&
|
||||
str[nextpos - 1] == k_arg_end) {
|
||||
chunk = mImpl->placeholder(chunk);
|
||||
}
|
||||
out.append(chunk);
|
||||
}
|
||||
if (pseudo && depth < mLastDepth) { // End of message
|
||||
out.append(mImpl->end());
|
||||
} else if (!pseudo && depth > mLastDepth) { // Start of message
|
||||
out.append(mImpl->start());
|
||||
}
|
||||
lastpos = nextpos;
|
||||
mLastDepth = depth;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static const char* pseudolocalizeChar(const char c) {
|
||||
switch (c) {
|
||||
case 'a': return "\u00e5";
|
||||
case 'b': return "\u0253";
|
||||
case 'c': return "\u00e7";
|
||||
case 'd': return "\u00f0";
|
||||
case 'e': return "\u00e9";
|
||||
case 'f': return "\u0192";
|
||||
case 'g': return "\u011d";
|
||||
case 'h': return "\u0125";
|
||||
case 'i': return "\u00ee";
|
||||
case 'j': return "\u0135";
|
||||
case 'k': return "\u0137";
|
||||
case 'l': return "\u013c";
|
||||
case 'm': return "\u1e3f";
|
||||
case 'n': return "\u00f1";
|
||||
case 'o': return "\u00f6";
|
||||
case 'p': return "\u00fe";
|
||||
case 'q': return "\u0051";
|
||||
case 'r': return "\u0155";
|
||||
case 's': return "\u0161";
|
||||
case 't': return "\u0163";
|
||||
case 'u': return "\u00fb";
|
||||
case 'v': return "\u0056";
|
||||
case 'w': return "\u0175";
|
||||
case 'x': return "\u0445";
|
||||
case 'y': return "\u00fd";
|
||||
case 'z': return "\u017e";
|
||||
case 'A': return "\u00c5";
|
||||
case 'B': return "\u03b2";
|
||||
case 'C': return "\u00c7";
|
||||
case 'D': return "\u00d0";
|
||||
case 'E': return "\u00c9";
|
||||
case 'G': return "\u011c";
|
||||
case 'H': return "\u0124";
|
||||
case 'I': return "\u00ce";
|
||||
case 'J': return "\u0134";
|
||||
case 'K': return "\u0136";
|
||||
case 'L': return "\u013b";
|
||||
case 'M': return "\u1e3e";
|
||||
case 'N': return "\u00d1";
|
||||
case 'O': return "\u00d6";
|
||||
case 'P': return "\u00de";
|
||||
case 'Q': return "\u0071";
|
||||
case 'R': return "\u0154";
|
||||
case 'S': return "\u0160";
|
||||
case 'T': return "\u0162";
|
||||
case 'U': return "\u00db";
|
||||
case 'V': return "\u03bd";
|
||||
case 'W': return "\u0174";
|
||||
case 'X': return "\u00d7";
|
||||
case 'Y': return "\u00dd";
|
||||
case 'Z': return "\u017d";
|
||||
case '!': return "\u00a1";
|
||||
case '?': return "\u00bf";
|
||||
case '$': return "\u20ac";
|
||||
default: return nullptr;
|
||||
}
|
||||
switch (c) {
|
||||
case 'a':
|
||||
return "\u00e5";
|
||||
case 'b':
|
||||
return "\u0253";
|
||||
case 'c':
|
||||
return "\u00e7";
|
||||
case 'd':
|
||||
return "\u00f0";
|
||||
case 'e':
|
||||
return "\u00e9";
|
||||
case 'f':
|
||||
return "\u0192";
|
||||
case 'g':
|
||||
return "\u011d";
|
||||
case 'h':
|
||||
return "\u0125";
|
||||
case 'i':
|
||||
return "\u00ee";
|
||||
case 'j':
|
||||
return "\u0135";
|
||||
case 'k':
|
||||
return "\u0137";
|
||||
case 'l':
|
||||
return "\u013c";
|
||||
case 'm':
|
||||
return "\u1e3f";
|
||||
case 'n':
|
||||
return "\u00f1";
|
||||
case 'o':
|
||||
return "\u00f6";
|
||||
case 'p':
|
||||
return "\u00fe";
|
||||
case 'q':
|
||||
return "\u0051";
|
||||
case 'r':
|
||||
return "\u0155";
|
||||
case 's':
|
||||
return "\u0161";
|
||||
case 't':
|
||||
return "\u0163";
|
||||
case 'u':
|
||||
return "\u00fb";
|
||||
case 'v':
|
||||
return "\u0056";
|
||||
case 'w':
|
||||
return "\u0175";
|
||||
case 'x':
|
||||
return "\u0445";
|
||||
case 'y':
|
||||
return "\u00fd";
|
||||
case 'z':
|
||||
return "\u017e";
|
||||
case 'A':
|
||||
return "\u00c5";
|
||||
case 'B':
|
||||
return "\u03b2";
|
||||
case 'C':
|
||||
return "\u00c7";
|
||||
case 'D':
|
||||
return "\u00d0";
|
||||
case 'E':
|
||||
return "\u00c9";
|
||||
case 'G':
|
||||
return "\u011c";
|
||||
case 'H':
|
||||
return "\u0124";
|
||||
case 'I':
|
||||
return "\u00ce";
|
||||
case 'J':
|
||||
return "\u0134";
|
||||
case 'K':
|
||||
return "\u0136";
|
||||
case 'L':
|
||||
return "\u013b";
|
||||
case 'M':
|
||||
return "\u1e3e";
|
||||
case 'N':
|
||||
return "\u00d1";
|
||||
case 'O':
|
||||
return "\u00d6";
|
||||
case 'P':
|
||||
return "\u00de";
|
||||
case 'Q':
|
||||
return "\u0071";
|
||||
case 'R':
|
||||
return "\u0154";
|
||||
case 'S':
|
||||
return "\u0160";
|
||||
case 'T':
|
||||
return "\u0162";
|
||||
case 'U':
|
||||
return "\u00db";
|
||||
case 'V':
|
||||
return "\u03bd";
|
||||
case 'W':
|
||||
return "\u0174";
|
||||
case 'X':
|
||||
return "\u00d7";
|
||||
case 'Y':
|
||||
return "\u00dd";
|
||||
case 'Z':
|
||||
return "\u017d";
|
||||
case '!':
|
||||
return "\u00a1";
|
||||
case '?':
|
||||
return "\u00bf";
|
||||
case '$':
|
||||
return "\u20ac";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPossibleNormalPlaceholderEnd(const char c) {
|
||||
switch (c) {
|
||||
case 's': return true;
|
||||
case 'S': return true;
|
||||
case 'c': return true;
|
||||
case 'C': return true;
|
||||
case 'd': return true;
|
||||
case 'o': return true;
|
||||
case 'x': return true;
|
||||
case 'X': return true;
|
||||
case 'f': return true;
|
||||
case 'e': return true;
|
||||
case 'E': return true;
|
||||
case 'g': return true;
|
||||
case 'G': return true;
|
||||
case 'a': return true;
|
||||
case 'A': return true;
|
||||
case 'b': return true;
|
||||
case 'B': return true;
|
||||
case 'h': return true;
|
||||
case 'H': return true;
|
||||
case '%': return true;
|
||||
case 'n': return true;
|
||||
default: return false;
|
||||
}
|
||||
switch (c) {
|
||||
case 's':
|
||||
return true;
|
||||
case 'S':
|
||||
return true;
|
||||
case 'c':
|
||||
return true;
|
||||
case 'C':
|
||||
return true;
|
||||
case 'd':
|
||||
return true;
|
||||
case 'o':
|
||||
return true;
|
||||
case 'x':
|
||||
return true;
|
||||
case 'X':
|
||||
return true;
|
||||
case 'f':
|
||||
return true;
|
||||
case 'e':
|
||||
return true;
|
||||
case 'E':
|
||||
return true;
|
||||
case 'g':
|
||||
return true;
|
||||
case 'G':
|
||||
return true;
|
||||
case 'a':
|
||||
return true;
|
||||
case 'A':
|
||||
return true;
|
||||
case 'b':
|
||||
return true;
|
||||
case 'B':
|
||||
return true;
|
||||
case 'h':
|
||||
return true;
|
||||
case 'H':
|
||||
return true;
|
||||
case '%':
|
||||
return true;
|
||||
case 'n':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string pseudoGenerateExpansion(const unsigned int length) {
|
||||
std::string result = k_expansion_string;
|
||||
const char* s = result.data();
|
||||
if (result.size() < length) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(length - result.size());
|
||||
} else {
|
||||
int ext = 0;
|
||||
// Should contain only whole words, so looking for a space
|
||||
for (unsigned int i = length + 1; i < result.size(); ++i) {
|
||||
++ext;
|
||||
if (s[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = result.substr(0, length + ext);
|
||||
std::string result = k_expansion_string;
|
||||
const char* s = result.data();
|
||||
if (result.size() < length) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(length - result.size());
|
||||
} else {
|
||||
int ext = 0;
|
||||
// Should contain only whole words, so looking for a space
|
||||
for (unsigned int i = length + 1; i < result.size(); ++i) {
|
||||
++ext;
|
||||
if (s[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
result = result.substr(0, length + ext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::start() {
|
||||
std::string result;
|
||||
if (mDepth == 0) {
|
||||
result = "[";
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth++;
|
||||
return result;
|
||||
std::string result;
|
||||
if (mDepth == 0) {
|
||||
result = "[";
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth++;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::end() {
|
||||
std::string result;
|
||||
if (mLength) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth--;
|
||||
if (mDepth == 0) {
|
||||
result += "]";
|
||||
}
|
||||
return result;
|
||||
std::string result;
|
||||
if (mLength) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth--;
|
||||
if (mDepth == 0) {
|
||||
result += "]";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,128 +349,125 @@ std::string PseudoMethodAccent::end() {
|
||||
*
|
||||
* Note: This leaves placeholder syntax untouched.
|
||||
*/
|
||||
std::string PseudoMethodAccent::text(const StringPiece& source)
|
||||
{
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
const size_t I = source.size();
|
||||
bool lastspace = true;
|
||||
for (size_t i = 0; i < I; i++) {
|
||||
char c = s[i];
|
||||
if (c == '%') {
|
||||
// Placeholder syntax, no need to pseudolocalize
|
||||
std::string chunk;
|
||||
bool end = false;
|
||||
chunk.append(&c, 1);
|
||||
while (!end && i + 1 < I) {
|
||||
++i;
|
||||
c = s[i];
|
||||
chunk.append(&c, 1);
|
||||
if (isPossibleNormalPlaceholderEnd(c)) {
|
||||
end = true;
|
||||
} else if (i + 1 < I && c == 't') {
|
||||
++i;
|
||||
c = s[i];
|
||||
chunk.append(&c, 1);
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
// Treat chunk as a placeholder unless it ends with %.
|
||||
result += ((c == '%') ? chunk : placeholder(chunk));
|
||||
} else if (c == '<' || c == '&') {
|
||||
// html syntax, no need to pseudolocalize
|
||||
bool tag_closed = false;
|
||||
while (!tag_closed && i < I) {
|
||||
if (c == '&') {
|
||||
std::string escapeText;
|
||||
escapeText.append(&c, 1);
|
||||
bool end = false;
|
||||
size_t htmlCodePos = i;
|
||||
while (!end && htmlCodePos < I) {
|
||||
++htmlCodePos;
|
||||
c = s[htmlCodePos];
|
||||
escapeText.append(&c, 1);
|
||||
// Valid html code
|
||||
if (c == ';') {
|
||||
end = true;
|
||||
i = htmlCodePos;
|
||||
}
|
||||
// Wrong html code
|
||||
else if (!((c == '#' ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9')))) {
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
result += escapeText;
|
||||
if (escapeText != "<") {
|
||||
tag_closed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '>') {
|
||||
tag_closed = true;
|
||||
result.append(&c, 1);
|
||||
continue;
|
||||
}
|
||||
result.append(&c, 1);
|
||||
i++;
|
||||
c = s[i];
|
||||
}
|
||||
} else {
|
||||
// This is a pure text that should be pseudolocalized
|
||||
const char* p = pseudolocalizeChar(c);
|
||||
if (p != nullptr) {
|
||||
result += p;
|
||||
} else {
|
||||
bool space = isspace(c);
|
||||
if (lastspace && !space) {
|
||||
mWordCount++;
|
||||
}
|
||||
lastspace = space;
|
||||
result.append(&c, 1);
|
||||
}
|
||||
// Count only pseudolocalizable chars and delimiters
|
||||
mLength++;
|
||||
std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
const size_t I = source.size();
|
||||
bool lastspace = true;
|
||||
for (size_t i = 0; i < I; i++) {
|
||||
char c = s[i];
|
||||
if (c == '%') {
|
||||
// Placeholder syntax, no need to pseudolocalize
|
||||
std::string chunk;
|
||||
bool end = false;
|
||||
chunk.append(&c, 1);
|
||||
while (!end && i + 1 < I) {
|
||||
++i;
|
||||
c = s[i];
|
||||
chunk.append(&c, 1);
|
||||
if (isPossibleNormalPlaceholderEnd(c)) {
|
||||
end = true;
|
||||
} else if (i + 1 < I && c == 't') {
|
||||
++i;
|
||||
c = s[i];
|
||||
chunk.append(&c, 1);
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
|
||||
// Surround a placeholder with brackets
|
||||
return k_placeholder_open + source.toString() + k_placeholder_close;
|
||||
}
|
||||
|
||||
std::string PseudoMethodBidi::text(const StringPiece& source) {
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
bool lastspace = true;
|
||||
bool space = true;
|
||||
for (size_t i = 0; i < source.size(); i++) {
|
||||
char c = s[i];
|
||||
space = isspace(c);
|
||||
}
|
||||
// Treat chunk as a placeholder unless it ends with %.
|
||||
result += ((c == '%') ? chunk : placeholder(chunk));
|
||||
} else if (c == '<' || c == '&') {
|
||||
// html syntax, no need to pseudolocalize
|
||||
bool tag_closed = false;
|
||||
while (!tag_closed && i < I) {
|
||||
if (c == '&') {
|
||||
std::string escapeText;
|
||||
escapeText.append(&c, 1);
|
||||
bool end = false;
|
||||
size_t htmlCodePos = i;
|
||||
while (!end && htmlCodePos < I) {
|
||||
++htmlCodePos;
|
||||
c = s[htmlCodePos];
|
||||
escapeText.append(&c, 1);
|
||||
// Valid html code
|
||||
if (c == ';') {
|
||||
end = true;
|
||||
i = htmlCodePos;
|
||||
}
|
||||
// Wrong html code
|
||||
else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) {
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
result += escapeText;
|
||||
if (escapeText != "<") {
|
||||
tag_closed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '>') {
|
||||
tag_closed = true;
|
||||
result.append(&c, 1);
|
||||
continue;
|
||||
}
|
||||
result.append(&c, 1);
|
||||
i++;
|
||||
c = s[i];
|
||||
}
|
||||
} else {
|
||||
// This is a pure text that should be pseudolocalized
|
||||
const char* p = pseudolocalizeChar(c);
|
||||
if (p != nullptr) {
|
||||
result += p;
|
||||
} else {
|
||||
bool space = isspace(c);
|
||||
if (lastspace && !space) {
|
||||
// Word start
|
||||
result += k_rlm + k_rlo;
|
||||
} else if (!lastspace && space) {
|
||||
// Word end
|
||||
result += k_pdf + k_rlm;
|
||||
mWordCount++;
|
||||
}
|
||||
lastspace = space;
|
||||
result.append(&c, 1);
|
||||
}
|
||||
// Count only pseudolocalizable chars and delimiters
|
||||
mLength++;
|
||||
}
|
||||
if (!lastspace) {
|
||||
// End of last word
|
||||
result += k_pdf + k_rlm;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
|
||||
// Surround a placeholder with brackets
|
||||
return k_placeholder_open + source.toString() + k_placeholder_close;
|
||||
}
|
||||
|
||||
std::string PseudoMethodBidi::text(const StringPiece& source) {
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
bool lastspace = true;
|
||||
bool space = true;
|
||||
for (size_t i = 0; i < source.size(); i++) {
|
||||
char c = s[i];
|
||||
space = isspace(c);
|
||||
if (lastspace && !space) {
|
||||
// Word start
|
||||
result += k_rlm + k_rlo;
|
||||
} else if (!lastspace && space) {
|
||||
// Word end
|
||||
result += k_pdf + k_rlm;
|
||||
}
|
||||
return result;
|
||||
lastspace = space;
|
||||
result.append(&c, 1);
|
||||
}
|
||||
if (!lastspace) {
|
||||
// End of last word
|
||||
result += k_pdf + k_rlm;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodBidi::placeholder(const StringPiece& source) {
|
||||
// Surround a placeholder with directionality change sequence
|
||||
return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
|
||||
// Surround a placeholder with directionality change sequence
|
||||
return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -27,32 +27,33 @@
|
||||
namespace aapt {
|
||||
|
||||
class PseudoMethodImpl {
|
||||
public:
|
||||
virtual ~PseudoMethodImpl() {}
|
||||
virtual std::string start() { return {}; }
|
||||
virtual std::string end() { return {}; }
|
||||
virtual std::string text(const StringPiece& text) = 0;
|
||||
virtual std::string placeholder(const StringPiece& text) = 0;
|
||||
public:
|
||||
virtual ~PseudoMethodImpl() {}
|
||||
virtual std::string start() { return {}; }
|
||||
virtual std::string end() { return {}; }
|
||||
virtual std::string text(const StringPiece& text) = 0;
|
||||
virtual std::string placeholder(const StringPiece& text) = 0;
|
||||
};
|
||||
|
||||
class Pseudolocalizer {
|
||||
public:
|
||||
enum class Method {
|
||||
kNone,
|
||||
kAccent,
|
||||
kBidi,
|
||||
};
|
||||
public:
|
||||
enum class Method {
|
||||
kNone,
|
||||
kAccent,
|
||||
kBidi,
|
||||
};
|
||||
|
||||
explicit Pseudolocalizer(Method method);
|
||||
void setMethod(Method method);
|
||||
std::string start() { return mImpl->start(); }
|
||||
std::string end() { return mImpl->end(); }
|
||||
std::string text(const StringPiece& text);
|
||||
private:
|
||||
std::unique_ptr<PseudoMethodImpl> mImpl;
|
||||
size_t mLastDepth;
|
||||
explicit Pseudolocalizer(Method method);
|
||||
void setMethod(Method method);
|
||||
std::string start() { return mImpl->start(); }
|
||||
std::string end() { return mImpl->end(); }
|
||||
std::string text(const StringPiece& text);
|
||||
|
||||
private:
|
||||
std::unique_ptr<PseudoMethodImpl> mImpl;
|
||||
size_t mLastDepth;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */
|
||||
|
||||
@@ -25,199 +25,207 @@ namespace aapt {
|
||||
// In this context, 'Axis' represents a particular field in the configuration,
|
||||
// such as language or density.
|
||||
|
||||
static ::testing::AssertionResult simpleHelper(const char* input, const char* expected,
|
||||
static ::testing::AssertionResult simpleHelper(const char* input,
|
||||
const char* expected,
|
||||
Pseudolocalizer::Method method) {
|
||||
Pseudolocalizer pseudo(method);
|
||||
std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
|
||||
if (result != expected) {
|
||||
return ::testing::AssertionFailure() << expected << " != " << result;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
Pseudolocalizer pseudo(method);
|
||||
std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
|
||||
if (result != expected) {
|
||||
return ::testing::AssertionFailure() << expected << " != " << result;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3,
|
||||
const char* expected,
|
||||
Pseudolocalizer::Method method) {
|
||||
Pseudolocalizer pseudo(method);
|
||||
std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) + pseudo.text(in3) +
|
||||
pseudo.end();
|
||||
if (result != expected) {
|
||||
return ::testing::AssertionFailure() << expected << " != " << result;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
static ::testing::AssertionResult compoundHelper(
|
||||
const char* in1, const char* in2, const char* in3, const char* expected,
|
||||
Pseudolocalizer::Method method) {
|
||||
Pseudolocalizer pseudo(method);
|
||||
std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) +
|
||||
pseudo.text(in3) + pseudo.end();
|
||||
if (result != expected) {
|
||||
return ::testing::AssertionFailure() << expected << " != " << result;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, NoPseudolocalization) {
|
||||
EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world",
|
||||
Pseudolocalizer::Method::kNone));
|
||||
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "",
|
||||
"Hello, world", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "", "Hello, world",
|
||||
Pseudolocalizer::Method::kNone));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, PlaintextAccent) {
|
||||
EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Hello, world",
|
||||
"[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper("Hello, %1d",
|
||||
"[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper("Battery %1d%%",
|
||||
"[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "",
|
||||
"[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(
|
||||
compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, PlaintextBidi) {
|
||||
EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper("word",
|
||||
"\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(" word ",
|
||||
" \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(" word ",
|
||||
" \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper("hello\n world\n",
|
||||
"\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
|
||||
" \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n",
|
||||
"\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
|
||||
" \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"word", "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
" word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
" word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("hello\n world\n",
|
||||
"\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
|
||||
" \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(compoundHelper(
|
||||
"hello", "\n ", " world\n",
|
||||
"\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
|
||||
" \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, SimpleICU) {
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
|
||||
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
|
||||
"[Ţöðåý îš »{1,date}« »{1,time}« one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
// Multi-fragment messages
|
||||
EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
|
||||
"[»{USER}« îš öƒƒļîñé one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("{USER} is offline",
|
||||
"[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
|
||||
EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
|
||||
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
|
||||
"[Ţöðåý îš »{1,date}« »{1,time}« one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
// Multi-fragment messages
|
||||
EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
|
||||
"[»{USER}« îš öƒƒļîñé one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
|
||||
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, ICUBidi) {
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("{placeholder}",
|
||||
"\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{COUNT, plural, one {one} other {other}}",
|
||||
"{COUNT, plural, " \
|
||||
"one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \
|
||||
"other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{placeholder}",
|
||||
"\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{COUNT, plural, one {one} other {other}}",
|
||||
"{COUNT, plural, "
|
||||
"one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} "
|
||||
"other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, Escaping) {
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("'{USER'} is offline",
|
||||
"['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("'{USER'} is offline",
|
||||
"['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
// Multi-fragment messages
|
||||
EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
|
||||
"['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
// Multi-fragment messages
|
||||
EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
|
||||
"['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, PluralsAndSelects) {
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
|
||||
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
|
||||
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
|
||||
"other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}",
|
||||
"[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} "
|
||||
"other {# ḿîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{1, select, female {{1} added you} "
|
||||
"male {{1} added you} other {{1} added you}}",
|
||||
"[{1, select, female {»{1}« åððéð ýöû one two} "
|
||||
"male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(
|
||||
compoundHelper("{COUNT, plural, one {Delete a file} "
|
||||
"other {Delete ",
|
||||
"{COUNT}", " files}}",
|
||||
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
|
||||
"other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"Distance is {COUNT, plural, one {# mile} other {# miles}}",
|
||||
"[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \
|
||||
"other {# ḿîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{1, select, female {{1} added you} " \
|
||||
"male {{1} added you} other {{1} added you}}",
|
||||
"[{1, select, female {»{1}« åððéð ýöû one two} " \
|
||||
"male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(compoundHelper(
|
||||
"{COUNT, plural, one {Delete a file} " \
|
||||
"other {Delete ", "{COUNT}", " files}}",
|
||||
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
|
||||
"other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, NestedICU) {
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
"{person, select, " \
|
||||
"female {" \
|
||||
"{num_circles, plural," \
|
||||
"=0{{person} didn't add you to any of her circles.}" \
|
||||
"=1{{person} added you to one of her circles.}" \
|
||||
"other{{person} added you to her # circles.}}}" \
|
||||
"male {" \
|
||||
"{num_circles, plural," \
|
||||
"=0{{person} didn't add you to any of his circles.}" \
|
||||
"=1{{person} added you to one of his circles.}" \
|
||||
"other{{person} added you to his # circles.}}}" \
|
||||
"other {" \
|
||||
"{num_circles, plural," \
|
||||
"=0{{person} didn't add you to any of their circles.}" \
|
||||
"=1{{person} added you to one of their circles.}" \
|
||||
"other{{person} added you to their # circles.}}}}",
|
||||
"[{person, select, " \
|
||||
"female {" \
|
||||
"{num_circles, plural," \
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \
|
||||
" one two three four five}" \
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \
|
||||
" one two three four}" \
|
||||
"other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \
|
||||
" one two three four}}}" \
|
||||
"male {" \
|
||||
"{num_circles, plural," \
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \
|
||||
" one two three four five}" \
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \
|
||||
" one two three four}" \
|
||||
"other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \
|
||||
" one two three four}}}" \
|
||||
"other {{num_circles, plural," \
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \
|
||||
" one two three four five}" \
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \
|
||||
" one two three four}" \
|
||||
"other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \
|
||||
" one two three four}}}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("{person, select, "
|
||||
"female {"
|
||||
"{num_circles, plural,"
|
||||
"=0{{person} didn't add you to any of her circles.}"
|
||||
"=1{{person} added you to one of her circles.}"
|
||||
"other{{person} added you to her # circles.}}}"
|
||||
"male {"
|
||||
"{num_circles, plural,"
|
||||
"=0{{person} didn't add you to any of his circles.}"
|
||||
"=1{{person} added you to one of his circles.}"
|
||||
"other{{person} added you to his # circles.}}}"
|
||||
"other {"
|
||||
"{num_circles, plural,"
|
||||
"=0{{person} didn't add you to any of their circles.}"
|
||||
"=1{{person} added you to one of their circles.}"
|
||||
"other{{person} added you to their # circles.}}}}",
|
||||
"[{person, select, "
|
||||
"female {"
|
||||
"{num_circles, plural,"
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš."
|
||||
" one two three four five}"
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš."
|
||||
" one two three four}"
|
||||
"other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš."
|
||||
" one two three four}}}"
|
||||
"male {"
|
||||
"{num_circles, plural,"
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš."
|
||||
" one two three four five}"
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš."
|
||||
" one two three four}"
|
||||
"other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš."
|
||||
" one two three four}}}"
|
||||
"other {{num_circles, plural,"
|
||||
"=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš."
|
||||
" one two three four five}"
|
||||
"=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš."
|
||||
" one two three four}"
|
||||
"other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš."
|
||||
" one two three four}}}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, RedefineMethod) {
|
||||
Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
|
||||
std::string result = pseudo.text("Hello, ");
|
||||
pseudo.setMethod(Pseudolocalizer::Method::kNone);
|
||||
result += pseudo.text("world!");
|
||||
ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
|
||||
Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
|
||||
std::string result = pseudo.text("Hello, ");
|
||||
pseudo.setMethod(Pseudolocalizer::Method::kNone);
|
||||
result += pseudo.text("world!");
|
||||
ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "compile/XmlIdCollector.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "compile/XmlIdCollector.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -27,44 +27,44 @@ namespace aapt {
|
||||
namespace {
|
||||
|
||||
static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
|
||||
return a.name < b;
|
||||
return a.name < b;
|
||||
}
|
||||
|
||||
struct IdCollector : public xml::Visitor {
|
||||
using xml::Visitor::visit;
|
||||
using xml::Visitor::visit;
|
||||
|
||||
std::vector<SourcedResourceName>* mOutSymbols;
|
||||
std::vector<SourcedResourceName>* mOutSymbols;
|
||||
|
||||
explicit IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
|
||||
}
|
||||
explicit IdCollector(std::vector<SourcedResourceName>* outSymbols)
|
||||
: mOutSymbols(outSymbols) {}
|
||||
|
||||
void visit(xml::Element* element) override {
|
||||
for (xml::Attribute& attr : element->attributes) {
|
||||
ResourceNameRef name;
|
||||
bool create = false;
|
||||
if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
|
||||
if (create && name.type == ResourceType::kId) {
|
||||
auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
|
||||
name, cmpName);
|
||||
if (iter == mOutSymbols->end() || iter->name != name) {
|
||||
mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
|
||||
element->lineNumber });
|
||||
}
|
||||
}
|
||||
}
|
||||
void visit(xml::Element* element) override {
|
||||
for (xml::Attribute& attr : element->attributes) {
|
||||
ResourceNameRef name;
|
||||
bool create = false;
|
||||
if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
|
||||
if (create && name.type == ResourceType::kId) {
|
||||
auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
|
||||
name, cmpName);
|
||||
if (iter == mOutSymbols->end() || iter->name != name) {
|
||||
mOutSymbols->insert(iter, SourcedResourceName{name.toResourceName(),
|
||||
element->lineNumber});
|
||||
}
|
||||
}
|
||||
|
||||
xml::Visitor::visit(element);
|
||||
}
|
||||
}
|
||||
|
||||
xml::Visitor::visit(element);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
|
||||
xmlRes->file.exportedSymbols.clear();
|
||||
IdCollector collector(&xmlRes->file.exportedSymbols);
|
||||
xmlRes->root->accept(&collector);
|
||||
return true;
|
||||
xmlRes->file.exportedSymbols.clear();
|
||||
IdCollector collector(&xmlRes->file.exportedSymbols);
|
||||
xmlRes->root->accept(&collector);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
namespace aapt {
|
||||
|
||||
struct XmlIdCollector : public IXmlResourceConsumer {
|
||||
bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
|
||||
bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_XMLIDCOLLECTOR_H */
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
#include "test/Builders.h"
|
||||
#include "test/Context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(XmlIdCollectorTest, CollectsIds) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/foo"
|
||||
text="@+id/bar">
|
||||
@@ -34,28 +34,35 @@ TEST(XmlIdCollectorTest, CollectsIds) {
|
||||
class="@+id/bar"/>
|
||||
</View>)EOF");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
|
||||
EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie("id/foo"), 3u }));
|
||||
EXPECT_EQ(
|
||||
1, std::count(doc->file.exportedSymbols.begin(),
|
||||
doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{test::parseNameOrDie("id/foo"), 3u}));
|
||||
|
||||
EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie("id/bar"), 3u }));
|
||||
EXPECT_EQ(
|
||||
1, std::count(doc->file.exportedSymbols.begin(),
|
||||
doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{test::parseNameOrDie("id/bar"), 3u}));
|
||||
|
||||
EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{ test::parseNameOrDie("id/car"), 6u }));
|
||||
EXPECT_EQ(
|
||||
1, std::count(doc->file.exportedSymbols.begin(),
|
||||
doc->file.exportedSymbols.end(),
|
||||
SourcedResourceName{test::parseNameOrDie("id/car"), 6u}));
|
||||
}
|
||||
|
||||
TEST(XmlIdCollectorTest, DontCollectNonIds) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
test::buildXmlDom("<View foo=\"@+string/foo\"/>");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
|
||||
EXPECT_TRUE(doc->file.exportedSymbols.empty());
|
||||
EXPECT_TRUE(doc->file.exportedSymbols.empty());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -27,412 +27,401 @@
|
||||
namespace aapt {
|
||||
|
||||
class DiffContext : public IAaptContext {
|
||||
public:
|
||||
const std::string& getCompilationPackage() override {
|
||||
return mEmpty;
|
||||
}
|
||||
public:
|
||||
const std::string& getCompilationPackage() override { return mEmpty; }
|
||||
|
||||
uint8_t getPackageId() override {
|
||||
return 0x0;
|
||||
}
|
||||
uint8_t getPackageId() override { return 0x0; }
|
||||
|
||||
IDiagnostics* getDiagnostics() override {
|
||||
return &mDiagnostics;
|
||||
}
|
||||
IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
|
||||
|
||||
NameMangler* getNameMangler() override {
|
||||
return &mNameMangler;
|
||||
}
|
||||
NameMangler* getNameMangler() override { return &mNameMangler; }
|
||||
|
||||
SymbolTable* getExternalSymbols() override {
|
||||
return &mSymbolTable;
|
||||
}
|
||||
SymbolTable* getExternalSymbols() override { return &mSymbolTable; }
|
||||
|
||||
bool verbose() override {
|
||||
return false;
|
||||
}
|
||||
bool verbose() override { return false; }
|
||||
|
||||
int getMinSdkVersion() override {
|
||||
return 0;
|
||||
}
|
||||
int getMinSdkVersion() override { return 0; }
|
||||
|
||||
private:
|
||||
std::string mEmpty;
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
|
||||
SymbolTable mSymbolTable;
|
||||
private:
|
||||
std::string mEmpty;
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
|
||||
SymbolTable mSymbolTable;
|
||||
};
|
||||
|
||||
class LoadedApk {
|
||||
public:
|
||||
LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
|
||||
std::unique_ptr<ResourceTable> table) :
|
||||
mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
|
||||
}
|
||||
public:
|
||||
LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
|
||||
std::unique_ptr<ResourceTable> table)
|
||||
: mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {}
|
||||
|
||||
io::IFileCollection* getFileCollection() {
|
||||
return mApk.get();
|
||||
}
|
||||
io::IFileCollection* getFileCollection() { return mApk.get(); }
|
||||
|
||||
ResourceTable* getResourceTable() {
|
||||
return mTable.get();
|
||||
}
|
||||
ResourceTable* getResourceTable() { return mTable.get(); }
|
||||
|
||||
const Source& getSource() {
|
||||
return mSource;
|
||||
}
|
||||
const Source& getSource() { return mSource; }
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
std::unique_ptr<io::IFileCollection> mApk;
|
||||
std::unique_ptr<ResourceTable> mTable;
|
||||
private:
|
||||
Source mSource;
|
||||
std::unique_ptr<io::IFileCollection> mApk;
|
||||
std::unique_ptr<ResourceTable> mTable;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
|
||||
};
|
||||
|
||||
static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
|
||||
Source source(path);
|
||||
std::string error;
|
||||
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
|
||||
if (!apk) {
|
||||
context->getDiagnostics()->error(DiagMessage(source) << error);
|
||||
return {};
|
||||
}
|
||||
static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context,
|
||||
const StringPiece& path) {
|
||||
Source source(path);
|
||||
std::string error;
|
||||
std::unique_ptr<io::ZipFileCollection> apk =
|
||||
io::ZipFileCollection::create(path, &error);
|
||||
if (!apk) {
|
||||
context->getDiagnostics()->error(DiagMessage(source) << error);
|
||||
return {};
|
||||
}
|
||||
|
||||
io::IFile* file = apk->findFile("resources.arsc");
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
|
||||
return {};
|
||||
}
|
||||
io::IFile* file = apk->findFile("resources.arsc");
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(source)
|
||||
<< "no resources.arsc found");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
|
||||
return {};
|
||||
}
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(source)
|
||||
<< "could not open resources.arsc");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
|
||||
if (!parser.parse()) {
|
||||
return {};
|
||||
}
|
||||
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), source, data->data(),
|
||||
data->size());
|
||||
if (!parser.parse()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
|
||||
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
|
||||
}
|
||||
|
||||
static void emitDiffLine(const Source& source, const StringPiece& message) {
|
||||
std::cerr << source << ": " << message << "\n";
|
||||
std::cerr << source << ": " << message << "\n";
|
||||
}
|
||||
|
||||
static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
|
||||
return symbolA.state != symbolB.state;
|
||||
static bool isSymbolVisibilityDifferent(const Symbol& symbolA,
|
||||
const Symbol& symbolB) {
|
||||
return symbolA.state != symbolB.state;
|
||||
}
|
||||
|
||||
template <typename Id>
|
||||
static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
|
||||
const Symbol& symbolB, const Maybe<Id>& idB) {
|
||||
if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
|
||||
return idA != idB;
|
||||
}
|
||||
return false;
|
||||
if (symbolA.state == SymbolState::kPublic ||
|
||||
symbolB.state == SymbolState::kPublic) {
|
||||
return idA != idB;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool emitResourceConfigValueDiff(IAaptContext* context,
|
||||
LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA,
|
||||
ResourceEntry* entryA,
|
||||
ResourceConfigValue* configValueA,
|
||||
LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB,
|
||||
ResourceTableType* typeB,
|
||||
ResourceEntry* entryB,
|
||||
ResourceConfigValue* configValueB) {
|
||||
Value* valueA = configValueA->value.get();
|
||||
Value* valueB = configValueB->value.get();
|
||||
if (!valueA->equals(valueB)) {
|
||||
std::stringstream strStream;
|
||||
strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " config=" << configValueA->config << " does not match:\n";
|
||||
valueA->print(&strStream);
|
||||
strStream << "\n vs \n";
|
||||
valueB->print(&strStream);
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
static bool emitResourceConfigValueDiff(
|
||||
IAaptContext* context, LoadedApk* apkA, ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA, ResourceEntry* entryA,
|
||||
ResourceConfigValue* configValueA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB, ResourceTableType* typeB, ResourceEntry* entryB,
|
||||
ResourceConfigValue* configValueB) {
|
||||
Value* valueA = configValueA->value.get();
|
||||
Value* valueB = configValueB->value.get();
|
||||
if (!valueA->equals(valueB)) {
|
||||
std::stringstream strStream;
|
||||
strStream << "value " << pkgA->name << ":" << typeA->type << "/"
|
||||
<< entryA->name << " config=" << configValueA->config
|
||||
<< " does not match:\n";
|
||||
valueA->print(&strStream);
|
||||
strStream << "\n vs \n";
|
||||
valueB->print(&strStream);
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool emitResourceEntryDiff(IAaptContext* context,
|
||||
LoadedApk* apkA,
|
||||
static bool emitResourceEntryDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA,
|
||||
ResourceEntry* entryA,
|
||||
LoadedApk* apkB,
|
||||
ResourceEntry* entryA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB,
|
||||
ResourceTableType* typeB,
|
||||
ResourceEntry* entryB) {
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
|
||||
ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
|
||||
if (!configValueB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " config=" << configValueA->config;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
|
||||
configValueA.get(), apkB, pkgB, typeB, entryB,
|
||||
configValueB);
|
||||
}
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
|
||||
ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
|
||||
if (!configValueB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type << "/"
|
||||
<< entryA->name << " config=" << configValueA->config;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
|
||||
configValueA.get(), apkB, pkgB, typeB,
|
||||
entryB, configValueB);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any newly added config values.
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
|
||||
ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
|
||||
if (!configValueA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
|
||||
<< " config=" << configValueB->config;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
// Check for any newly added config values.
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
|
||||
ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
|
||||
if (!configValueA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new config " << pkgB->name << ":" << typeB->type << "/"
|
||||
<< entryB->name << " config=" << configValueB->config;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool emitResourceTypeDiff(IAaptContext* context,
|
||||
LoadedApk* apkA,
|
||||
static bool emitResourceTypeDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA,
|
||||
LoadedApk* apkB,
|
||||
ResourceTableType* typeA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB,
|
||||
ResourceTableType* typeB) {
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
|
||||
ResourceEntry* entryB = typeB->findEntry(entryA->name);
|
||||
if (!entryB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
|
||||
ResourceEntry* entryB = typeB->findEntry(entryA->name);
|
||||
if (!entryB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type << "/"
|
||||
<< entryA->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
if (isSymbolVisibilityDifferent(entryA->symbolStatus,
|
||||
entryB->symbolStatus)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " has different visibility (";
|
||||
if (entryB->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " has different visibility (";
|
||||
if (entryB->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else if (isIdDiff(entryA->symbolStatus, entryA->id,
|
||||
entryB->symbolStatus, entryB->id)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " has different public ID (";
|
||||
if (entryB->id) {
|
||||
strStream << "0x" << std::hex << entryB->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->id) {
|
||||
strStream << "0x " << std::hex << entryA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
|
||||
apkB, pkgB, typeB, entryB);
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else if (isIdDiff(entryA->symbolStatus, entryA->id,
|
||||
entryB->symbolStatus, entryB->id)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
|
||||
<< " has different public ID (";
|
||||
if (entryB->id) {
|
||||
strStream << "0x" << std::hex << entryB->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->id) {
|
||||
strStream << "0x " << std::hex << entryA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
|
||||
apkB, pkgB, typeB, entryB);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any newly added entries.
|
||||
for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
|
||||
ResourceEntry* entryA = typeA->findEntry(entryB->name);
|
||||
if (!entryA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
// Check for any newly added entries.
|
||||
for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
|
||||
ResourceEntry* entryA = typeA->findEntry(entryB->name);
|
||||
if (!entryA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new entry " << pkgB->name << ":" << typeB->type << "/"
|
||||
<< entryB->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
LoadedApk* apkB, ResourceTablePackage* pkgB) {
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
|
||||
ResourceTableType* typeB = pkgB->findType(typeA->type);
|
||||
if (!typeB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type;
|
||||
emitDiffLine(apkA->getSource(), strStream.str());
|
||||
diff = true;
|
||||
ResourceTablePackage* pkgA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB) {
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
|
||||
ResourceTableType* typeB = pkgB->findType(typeA->type);
|
||||
if (!typeB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing " << pkgA->name << ":" << typeA->type;
|
||||
emitDiffLine(apkA->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
if (isSymbolVisibilityDifferent(typeA->symbolStatus,
|
||||
typeB->symbolStatus)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type
|
||||
<< " has different visibility (";
|
||||
if (typeB->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
|
||||
if (typeB->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
|
||||
if (typeB->id) {
|
||||
strStream << "0x" << std::hex << typeB->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->id) {
|
||||
strStream << "0x " << std::hex << typeA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus,
|
||||
typeB->id)) {
|
||||
std::stringstream strStream;
|
||||
strStream << pkgA->name << ":" << typeA->type
|
||||
<< " has different public ID (";
|
||||
if (typeB->id) {
|
||||
strStream << "0x" << std::hex << typeB->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->id) {
|
||||
strStream << "0x " << std::hex << typeA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB,
|
||||
typeB);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any newly added types.
|
||||
for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
|
||||
ResourceTableType* typeA = pkgA->findType(typeB->type);
|
||||
if (!typeA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new type " << pkgB->name << ":" << typeB->type;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
// Check for any newly added types.
|
||||
for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
|
||||
ResourceTableType* typeA = pkgA->findType(typeB->type);
|
||||
if (!typeA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new type " << pkgB->name << ":" << typeB->type;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
|
||||
ResourceTable* tableA = apkA->getResourceTable();
|
||||
ResourceTable* tableB = apkB->getResourceTable();
|
||||
static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
LoadedApk* apkB) {
|
||||
ResourceTable* tableA = apkA->getResourceTable();
|
||||
ResourceTable* tableB = apkB->getResourceTable();
|
||||
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
|
||||
ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
|
||||
if (!pkgB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing package " << pkgA->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
bool diff = false;
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
|
||||
ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
|
||||
if (!pkgB) {
|
||||
std::stringstream strStream;
|
||||
strStream << "missing package " << pkgA->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
if (pkgA->id != pkgB->id) {
|
||||
std::stringstream strStream;
|
||||
strStream << "package '" << pkgA->name << "' has different id (";
|
||||
if (pkgB->id) {
|
||||
strStream << "0x" << std::hex << pkgB->id.value();
|
||||
} else {
|
||||
if (pkgA->id != pkgB->id) {
|
||||
std::stringstream strStream;
|
||||
strStream << "package '" << pkgA->name << "' has different id (";
|
||||
if (pkgB->id) {
|
||||
strStream << "0x" << std::hex << pkgB->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (pkgA->id) {
|
||||
strStream << "0x" << std::hex << pkgA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (pkgA->id) {
|
||||
strStream << "0x" << std::hex << pkgA->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any newly added packages.
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
|
||||
ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
|
||||
if (!pkgA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new package " << pkgB->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
// Check for any newly added packages.
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
|
||||
ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
|
||||
if (!pkgA) {
|
||||
std::stringstream strStream;
|
||||
strStream << "new package " << pkgB->name;
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
diff = true;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
class ZeroingReferenceVisitor : public ValueVisitor {
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
if (ref->name && ref->id) {
|
||||
if (ref->id.value().packageId() == 0x7f) {
|
||||
ref->id = {};
|
||||
}
|
||||
}
|
||||
void visit(Reference* ref) override {
|
||||
if (ref->name && ref->id) {
|
||||
if (ref->id.value().packageId() == 0x7f) {
|
||||
ref->id = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void zeroOutAppReferences(ResourceTable* table) {
|
||||
ZeroingReferenceVisitor visitor;
|
||||
visitAllValuesInTable(table, &visitor);
|
||||
ZeroingReferenceVisitor visitor;
|
||||
visitAllValuesInTable(table, &visitor);
|
||||
}
|
||||
|
||||
int diff(const std::vector<StringPiece>& args) {
|
||||
DiffContext context;
|
||||
DiffContext context;
|
||||
|
||||
Flags flags;
|
||||
if (!flags.parse("aapt2 diff", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
Flags flags;
|
||||
if (!flags.parse("aapt2 diff", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (flags.getArgs().size() != 2u) {
|
||||
std::cerr << "must have two apks as arguments.\n\n";
|
||||
flags.usage("aapt2 diff", &std::cerr);
|
||||
return 1;
|
||||
}
|
||||
if (flags.getArgs().size() != 2u) {
|
||||
std::cerr << "must have two apks as arguments.\n\n";
|
||||
flags.usage("aapt2 diff", &std::cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
|
||||
std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
|
||||
if (!apkA || !apkB) {
|
||||
return 1;
|
||||
}
|
||||
std::unique_ptr<LoadedApk> apkA =
|
||||
loadApkFromPath(&context, flags.getArgs()[0]);
|
||||
std::unique_ptr<LoadedApk> apkB =
|
||||
loadApkFromPath(&context, flags.getArgs()[1]);
|
||||
if (!apkA || !apkB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Zero out Application IDs in references.
|
||||
zeroOutAppReferences(apkA->getResourceTable());
|
||||
zeroOutAppReferences(apkB->getResourceTable());
|
||||
// Zero out Application IDs in references.
|
||||
zeroOutAppReferences(apkA->getResourceTable());
|
||||
zeroOutAppReferences(apkB->getResourceTable());
|
||||
|
||||
if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
|
||||
// We emitted a diff, so return 1 (failure).
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
|
||||
// We emitted a diff, so return 1 (failure).
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -28,183 +28,181 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
//struct DumpOptions {
|
||||
// struct DumpOptions {
|
||||
//
|
||||
//};
|
||||
|
||||
void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len,
|
||||
const Source& source, IAaptContext* context) {
|
||||
std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
|
||||
context->getDiagnostics());
|
||||
if (!file) {
|
||||
context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data,
|
||||
size_t len, const Source& source, IAaptContext* context) {
|
||||
std::unique_ptr<ResourceFile> file =
|
||||
deserializeCompiledFileFromPb(pbFile, source, context->getDiagnostics());
|
||||
if (!file) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
<< "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Resource: " << file->name << "\n"
|
||||
<< "Config: " << file->config << "\n"
|
||||
<< "Source: " << file->source << "\n";
|
||||
std::cout << "Resource: " << file->name << "\n"
|
||||
<< "Config: " << file->config << "\n"
|
||||
<< "Source: " << file->source << "\n";
|
||||
}
|
||||
|
||||
void tryDumpFile(IAaptContext* context, const std::string& filePath) {
|
||||
std::unique_ptr<ResourceTable> table;
|
||||
std::unique_ptr<ResourceTable> table;
|
||||
|
||||
std::string err;
|
||||
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::create(filePath, &err);
|
||||
if (zip) {
|
||||
io::IFile* file = zip->findFile("resources.arsc.flat");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
<< "failed to open resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
std::string err;
|
||||
std::unique_ptr<io::ZipFileCollection> zip =
|
||||
io::ZipFileCollection::create(filePath, &err);
|
||||
if (zip) {
|
||||
io::IFile* file = zip->findFile("resources.arsc.flat");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(filePath) << "failed to open resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
|
||||
pb::ResourceTable pbTable;
|
||||
if (!pbTable.ParseFromArray(data->data(), data->size())) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
<< "invalid resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
pb::ResourceTable pbTable;
|
||||
if (!pbTable.ParseFromArray(data->data(), data->size())) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
<< "invalid resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
|
||||
table = deserializeTableFromPb(
|
||||
pbTable, Source(filePath), context->getDiagnostics());
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
file = zip->findFile("resources.arsc");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
<< "failed to open resources.arsc");
|
||||
return;
|
||||
}
|
||||
|
||||
table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), Source(filePath),
|
||||
data->data(), data->size());
|
||||
if (!parser.parse()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
table = deserializeTableFromPb(pbTable, Source(filePath),
|
||||
context->getDiagnostics());
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath) << err);
|
||||
return;
|
||||
file = zip->findFile("resources.arsc");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
<< "failed to open resources.arsc");
|
||||
return;
|
||||
}
|
||||
|
||||
android::FileMap* fileMap = &file.value();
|
||||
|
||||
// Try as a compiled table.
|
||||
pb::ResourceTable pbTable;
|
||||
if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
|
||||
table = deserializeTableFromPb(pbTable, Source(filePath), context->getDiagnostics());
|
||||
table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), Source(filePath),
|
||||
data->data(), data->size());
|
||||
if (!parser.parse()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
// Try as a compiled file.
|
||||
CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
|
||||
|
||||
uint32_t numFiles = 0;
|
||||
if (!input.ReadLittleEndian32(&numFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < numFiles; i++) {
|
||||
pb::CompiledFile compiledFile;
|
||||
if (!input.ReadCompiledFile(&compiledFile)) {
|
||||
context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t offset, len;
|
||||
if (!input.ReadDataMetaData(&offset, &len)) {
|
||||
context->getDiagnostics()->warn(DiagMessage() << "failed to read meta data");
|
||||
return;
|
||||
}
|
||||
|
||||
const void* data = static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset;
|
||||
dumpCompiledFile(compiledFile, data, len, Source(filePath), context);
|
||||
}
|
||||
}
|
||||
if (!table) {
|
||||
Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath) << err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (table) {
|
||||
DebugPrintTableOptions debugPrintTableOptions;
|
||||
debugPrintTableOptions.showSources = true;
|
||||
Debug::printTable(table.get(), debugPrintTableOptions);
|
||||
android::FileMap* fileMap = &file.value();
|
||||
|
||||
// Try as a compiled table.
|
||||
pb::ResourceTable pbTable;
|
||||
if (pbTable.ParseFromArray(fileMap->getDataPtr(),
|
||||
fileMap->getDataLength())) {
|
||||
table = deserializeTableFromPb(pbTable, Source(filePath),
|
||||
context->getDiagnostics());
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
// Try as a compiled file.
|
||||
CompiledFileInputStream input(fileMap->getDataPtr(),
|
||||
fileMap->getDataLength());
|
||||
|
||||
uint32_t numFiles = 0;
|
||||
if (!input.ReadLittleEndian32(&numFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < numFiles; i++) {
|
||||
pb::CompiledFile compiledFile;
|
||||
if (!input.ReadCompiledFile(&compiledFile)) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
<< "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t offset, len;
|
||||
if (!input.ReadDataMetaData(&offset, &len)) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
<< "failed to read meta data");
|
||||
return;
|
||||
}
|
||||
|
||||
const void* data =
|
||||
static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset;
|
||||
dumpCompiledFile(compiledFile, data, len, Source(filePath), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (table) {
|
||||
DebugPrintTableOptions debugPrintTableOptions;
|
||||
debugPrintTableOptions.showSources = true;
|
||||
Debug::printTable(table.get(), debugPrintTableOptions);
|
||||
}
|
||||
}
|
||||
|
||||
class DumpContext : public IAaptContext {
|
||||
public:
|
||||
IDiagnostics* getDiagnostics() override {
|
||||
return &mDiagnostics;
|
||||
}
|
||||
public:
|
||||
IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
|
||||
|
||||
NameMangler* getNameMangler() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
NameMangler* getNameMangler() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& getCompilationPackage() override {
|
||||
static std::string empty;
|
||||
return empty;
|
||||
}
|
||||
const std::string& getCompilationPackage() override {
|
||||
static std::string empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
uint8_t getPackageId() override {
|
||||
return 0;
|
||||
}
|
||||
uint8_t getPackageId() override { return 0; }
|
||||
|
||||
SymbolTable* getExternalSymbols() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
SymbolTable* getExternalSymbols() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool verbose() override {
|
||||
return mVerbose;
|
||||
}
|
||||
bool verbose() override { return mVerbose; }
|
||||
|
||||
void setVerbose(bool val) {
|
||||
mVerbose = val;
|
||||
}
|
||||
void setVerbose(bool val) { mVerbose = val; }
|
||||
|
||||
int getMinSdkVersion() override {
|
||||
return 0;
|
||||
}
|
||||
int getMinSdkVersion() override { return 0; }
|
||||
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
bool mVerbose = false;
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
bool mVerbose = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for dump command.
|
||||
*/
|
||||
int dump(const std::vector<StringPiece>& args) {
|
||||
bool verbose = false;
|
||||
Flags flags = Flags()
|
||||
.optionalSwitch("-v", "increase verbosity of output", &verbose);
|
||||
if (!flags.parse("aapt2 dump", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
bool verbose = false;
|
||||
Flags flags =
|
||||
Flags().optionalSwitch("-v", "increase verbosity of output", &verbose);
|
||||
if (!flags.parse("aapt2 dump", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DumpContext context;
|
||||
context.setVerbose(verbose);
|
||||
DumpContext context;
|
||||
context.setVerbose(verbose);
|
||||
|
||||
for (const std::string& arg : flags.getArgs()) {
|
||||
tryDumpFile(&context, arg);
|
||||
}
|
||||
return 0;
|
||||
for (const std::string& arg : flags.getArgs()) {
|
||||
tryDumpFile(&context, arg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -14,64 +14,68 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "filter/ConfigFilter.h"
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
void AxisConfigFilter::addConfig(ConfigDescription config) {
|
||||
uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
|
||||
uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
|
||||
|
||||
// Ignore the version
|
||||
diffMask &= ~android::ResTable_config::CONFIG_VERSION;
|
||||
// Ignore the version
|
||||
diffMask &= ~android::ResTable_config::CONFIG_VERSION;
|
||||
|
||||
// Ignore any densities. Those are best handled in --preferred-density
|
||||
if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
|
||||
config.density = 0;
|
||||
diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
|
||||
}
|
||||
// Ignore any densities. Those are best handled in --preferred-density
|
||||
if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
|
||||
config.density = 0;
|
||||
diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
|
||||
}
|
||||
|
||||
mConfigs.insert(std::make_pair(config, diffMask));
|
||||
mConfigMask |= diffMask;
|
||||
mConfigs.insert(std::make_pair(config, diffMask));
|
||||
mConfigMask |= diffMask;
|
||||
}
|
||||
|
||||
bool AxisConfigFilter::match(const ConfigDescription& config) const {
|
||||
const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
|
||||
if ((mConfigMask & mask) == 0) {
|
||||
// The two configurations don't have any common axis.
|
||||
return true;
|
||||
}
|
||||
const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
|
||||
if ((mConfigMask & mask) == 0) {
|
||||
// The two configurations don't have any common axis.
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t matchedAxis = 0;
|
||||
for (const auto& entry : mConfigs) {
|
||||
const ConfigDescription& target = entry.first;
|
||||
const uint32_t diffMask = entry.second;
|
||||
uint32_t diff = target.diff(config);
|
||||
if ((diff & diffMask) == 0) {
|
||||
// Mark the axis that was matched.
|
||||
matchedAxis |= diffMask;
|
||||
} else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
|
||||
// If the locales differ, but the languages are the same and
|
||||
// the locale we are matching only has a language specified,
|
||||
// we match.
|
||||
if (config.language[0] &&
|
||||
memcmp(config.language, target.language, sizeof(config.language)) == 0) {
|
||||
if (config.country[0] == 0) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
|
||||
}
|
||||
}
|
||||
} else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
|
||||
// Special case if the smallest screen width doesn't match. We check that the
|
||||
// config being matched has a smaller screen width than the filter specified.
|
||||
if (config.smallestScreenWidthDp != 0 &&
|
||||
config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
}
|
||||
uint32_t matchedAxis = 0;
|
||||
for (const auto& entry : mConfigs) {
|
||||
const ConfigDescription& target = entry.first;
|
||||
const uint32_t diffMask = entry.second;
|
||||
uint32_t diff = target.diff(config);
|
||||
if ((diff & diffMask) == 0) {
|
||||
// Mark the axis that was matched.
|
||||
matchedAxis |= diffMask;
|
||||
} else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
|
||||
// If the locales differ, but the languages are the same and
|
||||
// the locale we are matching only has a language specified,
|
||||
// we match.
|
||||
if (config.language[0] &&
|
||||
memcmp(config.language, target.language, sizeof(config.language)) ==
|
||||
0) {
|
||||
if (config.country[0] == 0) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
|
||||
}
|
||||
}
|
||||
} else if ((diff & diffMask) ==
|
||||
android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
|
||||
// Special case if the smallest screen width doesn't match. We check that
|
||||
// the
|
||||
// config being matched has a smaller screen width than the filter
|
||||
// specified.
|
||||
if (config.smallestScreenWidthDp != 0 &&
|
||||
config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
}
|
||||
}
|
||||
return matchedAxis == (mConfigMask & mask);
|
||||
}
|
||||
return matchedAxis == (mConfigMask & mask);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -28,34 +28,37 @@ namespace aapt {
|
||||
* Matches ConfigDescriptions based on some pattern.
|
||||
*/
|
||||
class IConfigFilter {
|
||||
public:
|
||||
virtual ~IConfigFilter() = default;
|
||||
public:
|
||||
virtual ~IConfigFilter() = default;
|
||||
|
||||
/**
|
||||
* Returns true if the filter matches the configuration, false otherwise.
|
||||
*/
|
||||
virtual bool match(const ConfigDescription& config) const = 0;
|
||||
/**
|
||||
* Returns true if the filter matches the configuration, false otherwise.
|
||||
*/
|
||||
virtual bool match(const ConfigDescription& config) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements config axis matching. An axis is one component of a configuration, like screen
|
||||
* density or locale. If an axis is specified in the filter, and the axis is specified in
|
||||
* the configuration to match, they must be compatible. Otherwise the configuration to match is
|
||||
* Implements config axis matching. An axis is one component of a configuration,
|
||||
* like screen
|
||||
* density or locale. If an axis is specified in the filter, and the axis is
|
||||
* specified in
|
||||
* the configuration to match, they must be compatible. Otherwise the
|
||||
* configuration to match is
|
||||
* accepted.
|
||||
*
|
||||
* Used when handling "-c" options.
|
||||
*/
|
||||
class AxisConfigFilter : public IConfigFilter {
|
||||
public:
|
||||
void addConfig(ConfigDescription config);
|
||||
public:
|
||||
void addConfig(ConfigDescription config);
|
||||
|
||||
bool match(const ConfigDescription& config) const override;
|
||||
bool match(const ConfigDescription& config) const override;
|
||||
|
||||
private:
|
||||
std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
|
||||
uint32_t mConfigMask = 0;
|
||||
private:
|
||||
std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
|
||||
uint32_t mConfigMask = 0;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FILTER_CONFIGFILTER_H */
|
||||
|
||||
@@ -22,91 +22,92 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
|
||||
AxisConfigFilter filter;
|
||||
AxisConfigFilter filter;
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr-rFR"));
|
||||
filter.addConfig(test::parseConfigOrDie("sw360dp"));
|
||||
filter.addConfig(test::parseConfigOrDie("normal"));
|
||||
filter.addConfig(test::parseConfigOrDie("en-rUS"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr-rFR"));
|
||||
filter.addConfig(test::parseConfigOrDie("sw360dp"));
|
||||
filter.addConfig(test::parseConfigOrDie("normal"));
|
||||
filter.addConfig(test::parseConfigOrDie("en-rUS"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr"));
|
||||
|
||||
EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
|
||||
EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr-rFR"));
|
||||
filter.addConfig(test::parseConfigOrDie("en-rUS"));
|
||||
filter.addConfig(test::parseConfigOrDie("normal"));
|
||||
filter.addConfig(test::parseConfigOrDie("large"));
|
||||
filter.addConfig(test::parseConfigOrDie("xxhdpi"));
|
||||
filter.addConfig(test::parseConfigOrDie("sw320dp"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("fr-rFR"));
|
||||
filter.addConfig(test::parseConfigOrDie("en-rUS"));
|
||||
filter.addConfig(test::parseConfigOrDie("normal"));
|
||||
filter.addConfig(test::parseConfigOrDie("large"));
|
||||
filter.addConfig(test::parseConfigOrDie("xxhdpi"));
|
||||
filter.addConfig(test::parseConfigOrDie("sw320dp"));
|
||||
|
||||
EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
|
||||
EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("sw600dp"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("sw600dp"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("de-rDE"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("de-rDE"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, IgnoresVersion) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("normal-v4"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("normal-v4"));
|
||||
|
||||
// The configs don't match on any axis besides version, which should be ignored.
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
|
||||
// The configs don't match on any axis besides version, which should be
|
||||
// ignored.
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
|
||||
}
|
||||
|
||||
TEST(ConfigFilterTest, MatchesConfigWithRegion) {
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("kok"));
|
||||
filter.addConfig(test::parseConfigOrDie("kok-rIN"));
|
||||
filter.addConfig(test::parseConfigOrDie("kok-v419"));
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("kok"));
|
||||
filter.addConfig(test::parseConfigOrDie("kok-rIN"));
|
||||
filter.addConfig(test::parseConfigOrDie("kok-v419"));
|
||||
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
|
||||
EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -18,167 +18,169 @@
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ziparchive/zip_writer.h>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ziparchive/zip_writer.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
struct DirectoryWriter : public IArchiveWriter {
|
||||
std::string mOutDir;
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
|
||||
std::string mOutDir;
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
|
||||
|
||||
bool open(IDiagnostics* diag, const StringPiece& outDir) {
|
||||
mOutDir = outDir.toString();
|
||||
file::FileType type = file::getFileType(mOutDir);
|
||||
if (type == file::FileType::kNonexistant) {
|
||||
diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
|
||||
return false;
|
||||
} else if (type != file::FileType::kDirectory) {
|
||||
diag->error(DiagMessage() << mOutDir << " is not a directory");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
bool open(IDiagnostics* diag, const StringPiece& outDir) {
|
||||
mOutDir = outDir.toString();
|
||||
file::FileType type = file::getFileType(mOutDir);
|
||||
if (type == file::FileType::kNonexistant) {
|
||||
diag->error(DiagMessage() << "directory " << mOutDir
|
||||
<< " does not exist");
|
||||
return false;
|
||||
} else if (type != file::FileType::kDirectory) {
|
||||
diag->error(DiagMessage() << mOutDir << " is not a directory");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (mFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (mFile) {
|
||||
return false;
|
||||
}
|
||||
std::string fullPath = mOutDir;
|
||||
file::appendPath(&fullPath, path);
|
||||
file::mkdirs(file::getStem(fullPath));
|
||||
|
||||
std::string fullPath = mOutDir;
|
||||
file::appendPath(&fullPath, path);
|
||||
file::mkdirs(file::getStem(fullPath));
|
||||
mFile = {fopen(fullPath.data(), "wb"), fclose};
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mFile = { fopen(fullPath.data(), "wb"), fclose };
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
|
||||
mFile.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeEntry(const void* data, size_t len) override {
|
||||
if (fwrite(data, 1, len, mFile.get()) != len) {
|
||||
mFile.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
|
||||
mFile.reset(nullptr);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeEntry(const void* data, size_t len) override {
|
||||
if (fwrite(data, 1, len, mFile.get()) != len) {
|
||||
mFile.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
if (!mFile) {
|
||||
return false;
|
||||
}
|
||||
mFile.reset(nullptr);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct ZipFileWriter : public IArchiveWriter {
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
|
||||
std::unique_ptr<ZipWriter> mWriter;
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
|
||||
std::unique_ptr<ZipWriter> mWriter;
|
||||
|
||||
bool open(IDiagnostics* diag, const StringPiece& path) {
|
||||
mFile = { fopen(path.data(), "w+b"), fclose };
|
||||
if (!mFile) {
|
||||
diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
mWriter = util::make_unique<ZipWriter>(mFile.get());
|
||||
return true;
|
||||
bool open(IDiagnostics* diag, const StringPiece& path) {
|
||||
mFile = {fopen(path.data(), "w+b"), fclose};
|
||||
if (!mFile) {
|
||||
diag->error(DiagMessage() << "failed to open " << path << ": "
|
||||
<< strerror(errno));
|
||||
return false;
|
||||
}
|
||||
mWriter = util::make_unique<ZipWriter>(mFile.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (!mWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (!mWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t zipFlags = 0;
|
||||
if (flags & ArchiveEntry::kCompress) {
|
||||
zipFlags |= ZipWriter::kCompress;
|
||||
}
|
||||
|
||||
if (flags & ArchiveEntry::kAlign) {
|
||||
zipFlags |= ZipWriter::kAlign32;
|
||||
}
|
||||
|
||||
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
size_t zipFlags = 0;
|
||||
if (flags & ArchiveEntry::kCompress) {
|
||||
zipFlags |= ZipWriter::kCompress;
|
||||
}
|
||||
|
||||
bool writeEntry(const void* data, size_t len) override {
|
||||
int32_t result = mWriter->WriteBytes(data, len);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (flags & ArchiveEntry::kAlign) {
|
||||
zipFlags |= ZipWriter::kAlign32;
|
||||
}
|
||||
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
int32_t result = mWriter->FinishEntry();
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
bool writeEntry(const void* data, size_t len) override {
|
||||
int32_t result = mWriter->WriteBytes(data, len);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ~ZipFileWriter() {
|
||||
if (mWriter) {
|
||||
mWriter->Finish();
|
||||
}
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
int32_t result = mWriter->FinishEntry();
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ~ZipFileWriter() {
|
||||
if (mWriter) {
|
||||
mWriter->Finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
|
||||
const StringPiece& path) {
|
||||
|
||||
std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path) {
|
||||
std::unique_ptr<DirectoryWriter> writer =
|
||||
util::make_unique<DirectoryWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
}
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
|
||||
const StringPiece& path) {
|
||||
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path) {
|
||||
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -31,37 +31,37 @@
|
||||
namespace aapt {
|
||||
|
||||
struct ArchiveEntry {
|
||||
enum : uint32_t {
|
||||
kCompress = 0x01,
|
||||
kAlign = 0x02,
|
||||
};
|
||||
enum : uint32_t {
|
||||
kCompress = 0x01,
|
||||
kAlign = 0x02,
|
||||
};
|
||||
|
||||
std::string path;
|
||||
uint32_t flags;
|
||||
size_t uncompressedSize;
|
||||
std::string path;
|
||||
uint32_t flags;
|
||||
size_t uncompressedSize;
|
||||
};
|
||||
|
||||
class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
|
||||
public:
|
||||
virtual ~IArchiveWriter() = default;
|
||||
public:
|
||||
virtual ~IArchiveWriter() = default;
|
||||
|
||||
virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
|
||||
virtual bool writeEntry(const BigBuffer& buffer) = 0;
|
||||
virtual bool writeEntry(const void* data, size_t len) = 0;
|
||||
virtual bool finishEntry() = 0;
|
||||
virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
|
||||
virtual bool writeEntry(const BigBuffer& buffer) = 0;
|
||||
virtual bool writeEntry(const void* data, size_t len) = 0;
|
||||
virtual bool finishEntry() = 0;
|
||||
|
||||
// CopyingOutputStream implementations.
|
||||
bool Write(const void* buffer, int size) override {
|
||||
return writeEntry(buffer, size);
|
||||
}
|
||||
// CopyingOutputStream implementations.
|
||||
bool Write(const void* buffer, int size) override {
|
||||
return writeEntry(buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
|
||||
const StringPiece& path);
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path);
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
|
||||
const StringPiece& path);
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path);
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_ARCHIVE_H */
|
||||
|
||||
@@ -25,63 +25,56 @@
|
||||
namespace aapt {
|
||||
|
||||
class ChunkWriter {
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
size_t mStartSize = 0;
|
||||
android::ResChunk_header* mHeader = nullptr;
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
size_t mStartSize = 0;
|
||||
android::ResChunk_header* mHeader = nullptr;
|
||||
|
||||
public:
|
||||
explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {
|
||||
}
|
||||
public:
|
||||
explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {}
|
||||
|
||||
ChunkWriter(const ChunkWriter&) = delete;
|
||||
ChunkWriter& operator=(const ChunkWriter&) = delete;
|
||||
ChunkWriter(ChunkWriter&&) = default;
|
||||
ChunkWriter& operator=(ChunkWriter&&) = default;
|
||||
ChunkWriter(const ChunkWriter&) = delete;
|
||||
ChunkWriter& operator=(const ChunkWriter&) = delete;
|
||||
ChunkWriter(ChunkWriter&&) = default;
|
||||
ChunkWriter& operator=(ChunkWriter&&) = default;
|
||||
|
||||
template <typename T>
|
||||
inline T* startChunk(uint16_t type) {
|
||||
mStartSize = mBuffer->size();
|
||||
T* chunk = mBuffer->nextBlock<T>();
|
||||
mHeader = &chunk->header;
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(T));
|
||||
return chunk;
|
||||
}
|
||||
template <typename T>
|
||||
inline T* startChunk(uint16_t type) {
|
||||
mStartSize = mBuffer->size();
|
||||
T* chunk = mBuffer->nextBlock<T>();
|
||||
mHeader = &chunk->header;
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(T));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T* nextBlock(size_t count = 1) {
|
||||
return mBuffer->nextBlock<T>(count);
|
||||
}
|
||||
template <typename T>
|
||||
inline T* nextBlock(size_t count = 1) {
|
||||
return mBuffer->nextBlock<T>(count);
|
||||
}
|
||||
|
||||
inline BigBuffer* getBuffer() {
|
||||
return mBuffer;
|
||||
}
|
||||
inline BigBuffer* getBuffer() { return mBuffer; }
|
||||
|
||||
inline android::ResChunk_header* getChunkHeader() {
|
||||
return mHeader;
|
||||
}
|
||||
inline android::ResChunk_header* getChunkHeader() { return mHeader; }
|
||||
|
||||
inline size_t size() {
|
||||
return mBuffer->size() - mStartSize;
|
||||
}
|
||||
inline size_t size() { return mBuffer->size() - mStartSize; }
|
||||
|
||||
inline android::ResChunk_header* finish() {
|
||||
mBuffer->align4();
|
||||
mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
|
||||
return mHeader;
|
||||
}
|
||||
inline android::ResChunk_header* finish() {
|
||||
mBuffer->align4();
|
||||
mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
|
||||
return mHeader;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) {
|
||||
mStartSize = mBuffer->size();
|
||||
mHeader = mBuffer->nextBlock<android::ResChunk_header>();
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
|
||||
return mHeader;
|
||||
mStartSize = mBuffer->size();
|
||||
mHeader = mBuffer->nextBlock<android::ResChunk_header>();
|
||||
mHeader->type = util::hostToDevice16(type);
|
||||
mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header));
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
|
||||
|
||||
@@ -22,15 +22,16 @@
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
|
||||
* An alternative struct to use instead of ResTable_map_entry. This one is a
|
||||
* standard_layout
|
||||
* struct.
|
||||
*/
|
||||
struct ResTable_entry_ext {
|
||||
android::ResTable_entry entry;
|
||||
android::ResTable_ref parent;
|
||||
uint32_t count;
|
||||
android::ResTable_entry entry;
|
||||
android::ResTable_ref parent;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <numeric>
|
||||
|
||||
using namespace android;
|
||||
|
||||
@@ -37,438 +37,454 @@ namespace {
|
||||
|
||||
template <typename T>
|
||||
static bool cmpIds(const T* a, const T* b) {
|
||||
return a->id.value() < b->id.value();
|
||||
return a->id.value() < b->id.value();
|
||||
}
|
||||
|
||||
static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
const char16_t* srcData = src.data();
|
||||
for (i = 0; i < len - 1 && i < src.size(); i++) {
|
||||
dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
|
||||
}
|
||||
dst[i] = 0;
|
||||
size_t i;
|
||||
const char16_t* srcData = src.data();
|
||||
for (i = 0; i < len - 1 && i < src.size(); i++) {
|
||||
dst[i] = util::hostToDevice16((uint16_t)srcData[i]);
|
||||
}
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
|
||||
if (a.key.id) {
|
||||
if (b.key.id) {
|
||||
return a.key.id.value() < b.key.id.value();
|
||||
}
|
||||
return true;
|
||||
} else if (!b.key.id) {
|
||||
return a.key.name.value() < b.key.name.value();
|
||||
}
|
||||
return false;
|
||||
if (a.key.id) {
|
||||
if (b.key.id) {
|
||||
return a.key.id.value() < b.key.id.value();
|
||||
}
|
||||
return true;
|
||||
} else if (!b.key.id) {
|
||||
return a.key.name.value() < b.key.name.value();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct FlatEntry {
|
||||
ResourceEntry* entry;
|
||||
Value* value;
|
||||
ResourceEntry* entry;
|
||||
Value* value;
|
||||
|
||||
// The entry string pool index to the entry's name.
|
||||
uint32_t entryKey;
|
||||
// The entry string pool index to the entry's name.
|
||||
uint32_t entryKey;
|
||||
};
|
||||
|
||||
class MapFlattenVisitor : public RawValueVisitor {
|
||||
public:
|
||||
using RawValueVisitor::visit;
|
||||
public:
|
||||
using RawValueVisitor::visit;
|
||||
|
||||
MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
|
||||
mOutEntry(outEntry), mBuffer(buffer) {
|
||||
MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer)
|
||||
: mOutEntry(outEntry), mBuffer(buffer) {}
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
{
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
{
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
if (attr->minInt != std::numeric_limits<int32_t>::min()) {
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
for (Attribute::Symbol& s : attr->symbols) {
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
|
||||
flattenEntry(&s.symbol, &val);
|
||||
}
|
||||
if (attr->minInt != std::numeric_limits<int32_t>::min()) {
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC,
|
||||
static_cast<uint32_t>(attr->minInt));
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
if (style->parent) {
|
||||
const Reference& parentRef = style->parent.value();
|
||||
assert(parentRef.id && "parent has no ID");
|
||||
mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
|
||||
}
|
||||
|
||||
// Sort the style.
|
||||
std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
flattenEntry(&entry.key, entry.value.get());
|
||||
}
|
||||
if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
|
||||
Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC,
|
||||
static_cast<uint32_t>(attr->maxInt));
|
||||
flattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
void visit(Styleable* styleable) override {
|
||||
for (auto& attrRef : styleable->entries) {
|
||||
BinaryPrimitive val(Res_value{});
|
||||
flattenEntry(&attrRef, &val);
|
||||
}
|
||||
for (Attribute::Symbol& s : attr->symbols) {
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
|
||||
flattenEntry(&s.symbol, &val);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
if (style->parent) {
|
||||
const Reference& parentRef = style->parent.value();
|
||||
assert(parentRef.id && "parent has no ID");
|
||||
mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
|
||||
}
|
||||
|
||||
void visit(Array* array) override {
|
||||
for (auto& item : array->items) {
|
||||
ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
|
||||
flattenValue(item.get(), outEntry);
|
||||
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
|
||||
mEntryCount++;
|
||||
}
|
||||
// Sort the style.
|
||||
std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
flattenEntry(&entry.key, entry.value.get());
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Plural* plural) override {
|
||||
const size_t count = plural->values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!plural->values[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceId q;
|
||||
switch (i) {
|
||||
case Plural::Zero:
|
||||
q.id = android::ResTable_map::ATTR_ZERO;
|
||||
break;
|
||||
|
||||
case Plural::One:
|
||||
q.id = android::ResTable_map::ATTR_ONE;
|
||||
break;
|
||||
|
||||
case Plural::Two:
|
||||
q.id = android::ResTable_map::ATTR_TWO;
|
||||
break;
|
||||
|
||||
case Plural::Few:
|
||||
q.id = android::ResTable_map::ATTR_FEW;
|
||||
break;
|
||||
|
||||
case Plural::Many:
|
||||
q.id = android::ResTable_map::ATTR_MANY;
|
||||
break;
|
||||
|
||||
case Plural::Other:
|
||||
q.id = android::ResTable_map::ATTR_OTHER;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
Reference key(q);
|
||||
flattenEntry(&key, plural->values[i].get());
|
||||
}
|
||||
void visit(Styleable* styleable) override {
|
||||
for (auto& attrRef : styleable->entries) {
|
||||
BinaryPrimitive val(Res_value{});
|
||||
flattenEntry(&attrRef, &val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this after visiting a Value. This will finish any work that
|
||||
* needs to be done to prepare the entry.
|
||||
*/
|
||||
void finish() {
|
||||
mOutEntry->count = util::hostToDevice32(mEntryCount);
|
||||
void visit(Array* array) override {
|
||||
for (auto& item : array->items) {
|
||||
ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
|
||||
flattenValue(item.get(), outEntry);
|
||||
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
|
||||
mEntryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void flattenKey(Reference* key, ResTable_map* outEntry) {
|
||||
assert(key->id && "key has no ID");
|
||||
outEntry->name.ident = util::hostToDevice32(key->id.value().id);
|
||||
void visit(Plural* plural) override {
|
||||
const size_t count = plural->values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!plural->values[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceId q;
|
||||
switch (i) {
|
||||
case Plural::Zero:
|
||||
q.id = android::ResTable_map::ATTR_ZERO;
|
||||
break;
|
||||
|
||||
case Plural::One:
|
||||
q.id = android::ResTable_map::ATTR_ONE;
|
||||
break;
|
||||
|
||||
case Plural::Two:
|
||||
q.id = android::ResTable_map::ATTR_TWO;
|
||||
break;
|
||||
|
||||
case Plural::Few:
|
||||
q.id = android::ResTable_map::ATTR_FEW;
|
||||
break;
|
||||
|
||||
case Plural::Many:
|
||||
q.id = android::ResTable_map::ATTR_MANY;
|
||||
break;
|
||||
|
||||
case Plural::Other:
|
||||
q.id = android::ResTable_map::ATTR_OTHER;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
Reference key(q);
|
||||
flattenEntry(&key, plural->values[i].get());
|
||||
}
|
||||
}
|
||||
|
||||
void flattenValue(Item* value, ResTable_map* outEntry) {
|
||||
bool result = value->flatten(&outEntry->value);
|
||||
assert(result && "flatten failed");
|
||||
}
|
||||
/**
|
||||
* Call this after visiting a Value. This will finish any work that
|
||||
* needs to be done to prepare the entry.
|
||||
*/
|
||||
void finish() { mOutEntry->count = util::hostToDevice32(mEntryCount); }
|
||||
|
||||
void flattenEntry(Reference* key, Item* value) {
|
||||
ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
|
||||
flattenKey(key, outEntry);
|
||||
flattenValue(value, outEntry);
|
||||
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
|
||||
mEntryCount++;
|
||||
}
|
||||
private:
|
||||
void flattenKey(Reference* key, ResTable_map* outEntry) {
|
||||
assert(key->id && "key has no ID");
|
||||
outEntry->name.ident = util::hostToDevice32(key->id.value().id);
|
||||
}
|
||||
|
||||
ResTable_entry_ext* mOutEntry;
|
||||
BigBuffer* mBuffer;
|
||||
size_t mEntryCount = 0;
|
||||
void flattenValue(Item* value, ResTable_map* outEntry) {
|
||||
bool result = value->flatten(&outEntry->value);
|
||||
assert(result && "flatten failed");
|
||||
}
|
||||
|
||||
void flattenEntry(Reference* key, Item* value) {
|
||||
ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
|
||||
flattenKey(key, outEntry);
|
||||
flattenValue(value, outEntry);
|
||||
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
|
||||
mEntryCount++;
|
||||
}
|
||||
|
||||
ResTable_entry_ext* mOutEntry;
|
||||
BigBuffer* mBuffer;
|
||||
size_t mEntryCount = 0;
|
||||
};
|
||||
|
||||
class PackageFlattener {
|
||||
public:
|
||||
PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
|
||||
mDiag(diag), mPackage(package) {
|
||||
public:
|
||||
PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
|
||||
: mDiag(diag), mPackage(package) {}
|
||||
|
||||
bool flattenPackage(BigBuffer* buffer) {
|
||||
ChunkWriter pkgWriter(buffer);
|
||||
ResTable_package* pkgHeader =
|
||||
pkgWriter.startChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
|
||||
pkgHeader->id = util::hostToDevice32(mPackage->id.value());
|
||||
|
||||
if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
|
||||
mDiag->error(DiagMessage() << "package name '" << mPackage->name
|
||||
<< "' is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flattenPackage(BigBuffer* buffer) {
|
||||
ChunkWriter pkgWriter(buffer);
|
||||
ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
|
||||
RES_TABLE_PACKAGE_TYPE);
|
||||
pkgHeader->id = util::hostToDevice32(mPackage->id.value());
|
||||
// Copy the package name in device endianness.
|
||||
strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
|
||||
util::utf8ToUtf16(mPackage->name));
|
||||
|
||||
if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
|
||||
mDiag->error(DiagMessage() <<
|
||||
"package name '" << mPackage->name << "' is too long");
|
||||
return false;
|
||||
}
|
||||
// Serialize the types. We do this now so that our type and key strings
|
||||
// are populated. We write those first.
|
||||
BigBuffer typeBuffer(1024);
|
||||
flattenTypes(&typeBuffer);
|
||||
|
||||
// Copy the package name in device endianness.
|
||||
strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
|
||||
util::utf8ToUtf16(mPackage->name));
|
||||
pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
|
||||
|
||||
// Serialize the types. We do this now so that our type and key strings
|
||||
// are populated. We write those first.
|
||||
BigBuffer typeBuffer(1024);
|
||||
flattenTypes(&typeBuffer);
|
||||
pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
|
||||
|
||||
pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
|
||||
// Append the types.
|
||||
buffer->appendBuffer(std::move(typeBuffer));
|
||||
|
||||
pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
|
||||
pkgWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Append the types.
|
||||
buffer->appendBuffer(std::move(typeBuffer));
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTablePackage* mPackage;
|
||||
StringPool mTypePool;
|
||||
StringPool mKeyPool;
|
||||
|
||||
pkgWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTablePackage* mPackage;
|
||||
StringPool mTypePool;
|
||||
StringPool mKeyPool;
|
||||
|
||||
template <typename T, bool IsItem>
|
||||
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
|
||||
static_assert(std::is_same<ResTable_entry, T>::value ||
|
||||
template <typename T, bool IsItem>
|
||||
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
|
||||
static_assert(std::is_same<ResTable_entry, T>::value ||
|
||||
std::is_same<ResTable_entry_ext, T>::value,
|
||||
"T must be ResTable_entry or ResTable_entry_ext");
|
||||
"T must be ResTable_entry or ResTable_entry_ext");
|
||||
|
||||
T* result = buffer->nextBlock<T>();
|
||||
ResTable_entry* outEntry = (ResTable_entry*)(result);
|
||||
if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
if (entry->value->isWeak()) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
if (!IsItem) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
|
||||
}
|
||||
|
||||
outEntry->flags = util::hostToDevice16(outEntry->flags);
|
||||
outEntry->key.index = util::hostToDevice32(entry->entryKey);
|
||||
outEntry->size = util::hostToDevice16(sizeof(T));
|
||||
return result;
|
||||
T* result = buffer->nextBlock<T>();
|
||||
ResTable_entry* outEntry = (ResTable_entry*)(result);
|
||||
if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
|
||||
if (Item* item = valueCast<Item>(entry->value)) {
|
||||
writeEntry<ResTable_entry, true>(entry, buffer);
|
||||
Res_value* outValue = buffer->nextBlock<Res_value>();
|
||||
bool result = item->flatten(outValue);
|
||||
assert(result && "flatten failed");
|
||||
outValue->size = util::hostToDevice16(sizeof(*outValue));
|
||||
} else {
|
||||
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
|
||||
MapFlattenVisitor visitor(outEntry, buffer);
|
||||
entry->value->accept(&visitor);
|
||||
visitor.finish();
|
||||
}
|
||||
return true;
|
||||
if (entry->value->isWeak()) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
|
||||
std::vector<FlatEntry>* entries, BigBuffer* buffer) {
|
||||
ChunkWriter typeWriter(buffer);
|
||||
ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
|
||||
typeHeader->id = type->id.value();
|
||||
typeHeader->config = config;
|
||||
typeHeader->config.swapHtoD();
|
||||
|
||||
auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
|
||||
return std::max(max, (uint32_t) a->id.value());
|
||||
};
|
||||
|
||||
// Find the largest entry ID. That is how many entries we will have.
|
||||
const uint32_t entryCount =
|
||||
std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
|
||||
|
||||
typeHeader->entryCount = util::hostToDevice32(entryCount);
|
||||
uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
|
||||
|
||||
assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
|
||||
memset(indices, 0xff, entryCount * sizeof(uint32_t));
|
||||
|
||||
typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
|
||||
|
||||
const size_t entryStart = typeWriter.getBuffer()->size();
|
||||
for (FlatEntry& flatEntry : *entries) {
|
||||
assert(flatEntry.entry->id.value() < entryCount);
|
||||
indices[flatEntry.entry->id.value()] = util::hostToDevice32(
|
||||
typeWriter.getBuffer()->size() - entryStart);
|
||||
if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
|
||||
mDiag->error(DiagMessage()
|
||||
<< "failed to flatten resource '"
|
||||
<< ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
|
||||
<< "' for configuration '" << config << "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
typeWriter.finish();
|
||||
return true;
|
||||
if (!IsItem) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
|
||||
}
|
||||
|
||||
std::vector<ResourceTableType*> collectAndSortTypes() {
|
||||
std::vector<ResourceTableType*> sortedTypes;
|
||||
for (auto& type : mPackage->types) {
|
||||
if (type->type == ResourceType::kStyleable) {
|
||||
// Styleables aren't real Resource Types, they are represented in the R.java
|
||||
// file.
|
||||
continue;
|
||||
}
|
||||
outEntry->flags = util::hostToDevice16(outEntry->flags);
|
||||
outEntry->key.index = util::hostToDevice32(entry->entryKey);
|
||||
outEntry->size = util::hostToDevice16(sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
assert(type->id && "type must have an ID set");
|
||||
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
|
||||
if (Item* item = valueCast<Item>(entry->value)) {
|
||||
writeEntry<ResTable_entry, true>(entry, buffer);
|
||||
Res_value* outValue = buffer->nextBlock<Res_value>();
|
||||
bool result = item->flatten(outValue);
|
||||
assert(result && "flatten failed");
|
||||
outValue->size = util::hostToDevice16(sizeof(*outValue));
|
||||
} else {
|
||||
ResTable_entry_ext* outEntry =
|
||||
writeEntry<ResTable_entry_ext, false>(entry, buffer);
|
||||
MapFlattenVisitor visitor(outEntry, buffer);
|
||||
entry->value->accept(&visitor);
|
||||
visitor.finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
sortedTypes.push_back(type.get());
|
||||
}
|
||||
std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
|
||||
return sortedTypes;
|
||||
bool flattenConfig(const ResourceTableType* type,
|
||||
const ConfigDescription& config,
|
||||
std::vector<FlatEntry>* entries, BigBuffer* buffer) {
|
||||
ChunkWriter typeWriter(buffer);
|
||||
ResTable_type* typeHeader =
|
||||
typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
|
||||
typeHeader->id = type->id.value();
|
||||
typeHeader->config = config;
|
||||
typeHeader->config.swapHtoD();
|
||||
|
||||
auto maxAccum = [](uint32_t max,
|
||||
const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
|
||||
return std::max(max, (uint32_t)a->id.value());
|
||||
};
|
||||
|
||||
// Find the largest entry ID. That is how many entries we will have.
|
||||
const uint32_t entryCount =
|
||||
std::accumulate(type->entries.begin(), type->entries.end(), 0,
|
||||
maxAccum) +
|
||||
1;
|
||||
|
||||
typeHeader->entryCount = util::hostToDevice32(entryCount);
|
||||
uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
|
||||
|
||||
assert((size_t)entryCount <= std::numeric_limits<uint16_t>::max() + 1);
|
||||
memset(indices, 0xff, entryCount * sizeof(uint32_t));
|
||||
|
||||
typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
|
||||
|
||||
const size_t entryStart = typeWriter.getBuffer()->size();
|
||||
for (FlatEntry& flatEntry : *entries) {
|
||||
assert(flatEntry.entry->id.value() < entryCount);
|
||||
indices[flatEntry.entry->id.value()] =
|
||||
util::hostToDevice32(typeWriter.getBuffer()->size() - entryStart);
|
||||
if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
|
||||
mDiag->error(DiagMessage()
|
||||
<< "failed to flatten resource '"
|
||||
<< ResourceNameRef(mPackage->name, type->type,
|
||||
flatEntry.entry->name)
|
||||
<< "' for configuration '" << config << "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
typeWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ResourceTableType*> collectAndSortTypes() {
|
||||
std::vector<ResourceTableType*> sortedTypes;
|
||||
for (auto& type : mPackage->types) {
|
||||
if (type->type == ResourceType::kStyleable) {
|
||||
// Styleables aren't real Resource Types, they are represented in the
|
||||
// R.java
|
||||
// file.
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(type->id && "type must have an ID set");
|
||||
|
||||
sortedTypes.push_back(type.get());
|
||||
}
|
||||
std::sort(sortedTypes.begin(), sortedTypes.end(),
|
||||
cmpIds<ResourceTableType>);
|
||||
return sortedTypes;
|
||||
}
|
||||
|
||||
std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
|
||||
// Sort the entries by entry ID.
|
||||
std::vector<ResourceEntry*> sortedEntries;
|
||||
for (auto& entry : type->entries) {
|
||||
assert(entry->id && "entry must have an ID set");
|
||||
sortedEntries.push_back(entry.get());
|
||||
}
|
||||
std::sort(sortedEntries.begin(), sortedEntries.end(),
|
||||
cmpIds<ResourceEntry>);
|
||||
return sortedEntries;
|
||||
}
|
||||
|
||||
bool flattenTypeSpec(ResourceTableType* type,
|
||||
std::vector<ResourceEntry*>* sortedEntries,
|
||||
BigBuffer* buffer) {
|
||||
ChunkWriter typeSpecWriter(buffer);
|
||||
ResTable_typeSpec* specHeader =
|
||||
typeSpecWriter.startChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
|
||||
specHeader->id = type->id.value();
|
||||
|
||||
if (sortedEntries->empty()) {
|
||||
typeSpecWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
|
||||
// Sort the entries by entry ID.
|
||||
std::vector<ResourceEntry*> sortedEntries;
|
||||
for (auto& entry : type->entries) {
|
||||
assert(entry->id && "entry must have an ID set");
|
||||
sortedEntries.push_back(entry.get());
|
||||
// We can't just take the size of the vector. There may be holes in the
|
||||
// entry ID space.
|
||||
// Since the entries are sorted by ID, the last one will be the biggest.
|
||||
const size_t numEntries = sortedEntries->back()->id.value() + 1;
|
||||
|
||||
specHeader->entryCount = util::hostToDevice32(numEntries);
|
||||
|
||||
// Reserve space for the masks of each resource in this type. These
|
||||
// show for which configuration axis the resource changes.
|
||||
uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
|
||||
|
||||
const size_t actualNumEntries = sortedEntries->size();
|
||||
for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
|
||||
ResourceEntry* entry = sortedEntries->at(entryIndex);
|
||||
|
||||
// Populate the config masks for this entry.
|
||||
|
||||
if (entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
configMasks[entry->id.value()] |=
|
||||
util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
|
||||
}
|
||||
|
||||
const size_t configCount = entry->values.size();
|
||||
for (size_t i = 0; i < configCount; i++) {
|
||||
const ConfigDescription& config = entry->values[i]->config;
|
||||
for (size_t j = i + 1; j < configCount; j++) {
|
||||
configMasks[entry->id.value()] |=
|
||||
util::hostToDevice32(config.diff(entry->values[j]->config));
|
||||
}
|
||||
std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
|
||||
return sortedEntries;
|
||||
}
|
||||
}
|
||||
typeSpecWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
|
||||
BigBuffer* buffer) {
|
||||
ChunkWriter typeSpecWriter(buffer);
|
||||
ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
|
||||
RES_TABLE_TYPE_SPEC_TYPE);
|
||||
specHeader->id = type->id.value();
|
||||
bool flattenTypes(BigBuffer* buffer) {
|
||||
// Sort the types by their IDs. They will be inserted into the StringPool in
|
||||
// this order.
|
||||
std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
|
||||
|
||||
if (sortedEntries->empty()) {
|
||||
typeSpecWriter.finish();
|
||||
return true;
|
||||
size_t expectedTypeId = 1;
|
||||
for (ResourceTableType* type : sortedTypes) {
|
||||
// If there is a gap in the type IDs, fill in the StringPool
|
||||
// with empty values until we reach the ID we expect.
|
||||
while (type->id.value() > expectedTypeId) {
|
||||
std::stringstream typeName;
|
||||
typeName << "?" << expectedTypeId;
|
||||
mTypePool.makeRef(typeName.str());
|
||||
expectedTypeId++;
|
||||
}
|
||||
expectedTypeId++;
|
||||
mTypePool.makeRef(toString(type->type));
|
||||
|
||||
std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
|
||||
|
||||
if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The binary resource table lists resource entries for each
|
||||
// configuration.
|
||||
// We store them inverted, where a resource entry lists the values for
|
||||
// each
|
||||
// configuration available. Here we reverse this to match the binary
|
||||
// table.
|
||||
std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
|
||||
for (ResourceEntry* entry : sortedEntries) {
|
||||
const uint32_t keyIndex =
|
||||
(uint32_t)mKeyPool.makeRef(entry->name).getIndex();
|
||||
|
||||
// Group values by configuration.
|
||||
for (auto& configValue : entry->values) {
|
||||
configToEntryListMap[configValue->config].push_back(
|
||||
FlatEntry{entry, configValue->value.get(), keyIndex});
|
||||
}
|
||||
}
|
||||
|
||||
// We can't just take the size of the vector. There may be holes in the entry ID space.
|
||||
// Since the entries are sorted by ID, the last one will be the biggest.
|
||||
const size_t numEntries = sortedEntries->back()->id.value() + 1;
|
||||
|
||||
specHeader->entryCount = util::hostToDevice32(numEntries);
|
||||
|
||||
// Reserve space for the masks of each resource in this type. These
|
||||
// show for which configuration axis the resource changes.
|
||||
uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
|
||||
|
||||
const size_t actualNumEntries = sortedEntries->size();
|
||||
for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
|
||||
ResourceEntry* entry = sortedEntries->at(entryIndex);
|
||||
|
||||
// Populate the config masks for this entry.
|
||||
|
||||
if (entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
configMasks[entry->id.value()] |=
|
||||
util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
|
||||
}
|
||||
|
||||
const size_t configCount = entry->values.size();
|
||||
for (size_t i = 0; i < configCount; i++) {
|
||||
const ConfigDescription& config = entry->values[i]->config;
|
||||
for (size_t j = i + 1; j < configCount; j++) {
|
||||
configMasks[entry->id.value()] |= util::hostToDevice32(
|
||||
config.diff(entry->values[j]->config));
|
||||
}
|
||||
}
|
||||
// Flatten a configuration value.
|
||||
for (auto& entry : configToEntryListMap) {
|
||||
if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
|
||||
return false;
|
||||
}
|
||||
typeSpecWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flattenTypes(BigBuffer* buffer) {
|
||||
// Sort the types by their IDs. They will be inserted into the StringPool in this order.
|
||||
std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
|
||||
|
||||
size_t expectedTypeId = 1;
|
||||
for (ResourceTableType* type : sortedTypes) {
|
||||
// If there is a gap in the type IDs, fill in the StringPool
|
||||
// with empty values until we reach the ID we expect.
|
||||
while (type->id.value() > expectedTypeId) {
|
||||
std::stringstream typeName;
|
||||
typeName << "?" << expectedTypeId;
|
||||
mTypePool.makeRef(typeName.str());
|
||||
expectedTypeId++;
|
||||
}
|
||||
expectedTypeId++;
|
||||
mTypePool.makeRef(toString(type->type));
|
||||
|
||||
std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
|
||||
|
||||
if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The binary resource table lists resource entries for each configuration.
|
||||
// We store them inverted, where a resource entry lists the values for each
|
||||
// configuration available. Here we reverse this to match the binary table.
|
||||
std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
|
||||
for (ResourceEntry* entry : sortedEntries) {
|
||||
const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
|
||||
|
||||
// Group values by configuration.
|
||||
for (auto& configValue : entry->values) {
|
||||
configToEntryListMap[configValue->config].push_back(FlatEntry{
|
||||
entry, configValue->value.get(), keyIndex });
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten a configuration value.
|
||||
for (auto& entry : configToEntryListMap) {
|
||||
if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// We must do this before writing the resources, since the string pool IDs may change.
|
||||
table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
// We must do this before writing the resources, since the string pool IDs may
|
||||
// change.
|
||||
table->stringPool.sort(
|
||||
[](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
int diff = a.context.priority - b.context.priority;
|
||||
if (diff < 0) return true;
|
||||
if (diff > 0) return false;
|
||||
@@ -476,31 +492,32 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
|
||||
if (diff < 0) return true;
|
||||
if (diff > 0) return false;
|
||||
return a.value < b.value;
|
||||
});
|
||||
table->stringPool.prune();
|
||||
});
|
||||
table->stringPool.prune();
|
||||
|
||||
// Write the ResTable header.
|
||||
ChunkWriter tableWriter(mBuffer);
|
||||
ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
|
||||
tableHeader->packageCount = util::hostToDevice32(table->packages.size());
|
||||
// Write the ResTable header.
|
||||
ChunkWriter tableWriter(mBuffer);
|
||||
ResTable_header* tableHeader =
|
||||
tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
|
||||
tableHeader->packageCount = util::hostToDevice32(table->packages.size());
|
||||
|
||||
// Flatten the values string pool.
|
||||
StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
|
||||
// Flatten the values string pool.
|
||||
StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
|
||||
|
||||
BigBuffer packageBuffer(1024);
|
||||
BigBuffer packageBuffer(1024);
|
||||
|
||||
// Flatten each package.
|
||||
for (auto& package : table->packages) {
|
||||
PackageFlattener flattener(context->getDiagnostics(), package.get());
|
||||
if (!flattener.flattenPackage(&packageBuffer)) {
|
||||
return false;
|
||||
}
|
||||
// Flatten each package.
|
||||
for (auto& package : table->packages) {
|
||||
PackageFlattener flattener(context->getDiagnostics(), package.get());
|
||||
if (!flattener.flattenPackage(&packageBuffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally merge all the packages into the main buffer.
|
||||
tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
|
||||
tableWriter.finish();
|
||||
return true;
|
||||
// Finally merge all the packages into the main buffer.
|
||||
tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
|
||||
tableWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -25,16 +25,15 @@ class BigBuffer;
|
||||
class ResourceTable;
|
||||
|
||||
class TableFlattener : public IResourceTableConsumer {
|
||||
public:
|
||||
explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
|
||||
}
|
||||
public:
|
||||
explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {}
|
||||
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceUtils.h"
|
||||
#include "flatten/TableFlattener.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "test/Test.h"
|
||||
#include "unflatten/BinaryResourceParser.h"
|
||||
#include "util/Util.h"
|
||||
@@ -25,195 +25,207 @@ using namespace android;
|
||||
namespace aapt {
|
||||
|
||||
class TableFlattenerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setPackageId(0x7f)
|
||||
.build();
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setPackageId(0x7f)
|
||||
.build();
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
|
||||
}
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
::testing::AssertionResult flatten(ResourceTable* table,
|
||||
ResourceTable* outTable) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
|
||||
}
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(),
|
||||
buffer.size());
|
||||
if (!parser.parse()) {
|
||||
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size());
|
||||
if (!parser.parse()) {
|
||||
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
::testing::AssertionResult exists(ResTable* table,
|
||||
const StringPiece& expectedName,
|
||||
const ResourceId& expectedId,
|
||||
const ConfigDescription& expectedConfig,
|
||||
const uint8_t expectedDataType,
|
||||
const uint32_t expectedData,
|
||||
const uint32_t expectedSpecFlags) {
|
||||
const ResourceName expectedResName = test::parseNameOrDie(expectedName);
|
||||
|
||||
table->setParameters(&expectedConfig);
|
||||
|
||||
ResTable_config config;
|
||||
Res_value val;
|
||||
uint32_t specFlags;
|
||||
if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) <
|
||||
0) {
|
||||
return ::testing::AssertionFailure() << "could not find resource with";
|
||||
}
|
||||
|
||||
::testing::AssertionResult exists(ResTable* table,
|
||||
const StringPiece& expectedName,
|
||||
const ResourceId& expectedId,
|
||||
const ConfigDescription& expectedConfig,
|
||||
const uint8_t expectedDataType, const uint32_t expectedData,
|
||||
const uint32_t expectedSpecFlags) {
|
||||
const ResourceName expectedResName = test::parseNameOrDie(expectedName);
|
||||
|
||||
table->setParameters(&expectedConfig);
|
||||
|
||||
ResTable_config config;
|
||||
Res_value val;
|
||||
uint32_t specFlags;
|
||||
if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) {
|
||||
return ::testing::AssertionFailure() << "could not find resource with";
|
||||
}
|
||||
|
||||
if (expectedDataType != val.dataType) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data type "
|
||||
<< std::hex << (int) expectedDataType << " but got data type "
|
||||
<< (int) val.dataType << std::dec << " instead";
|
||||
}
|
||||
|
||||
if (expectedData != val.data) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data "
|
||||
<< std::hex << expectedData << " but got data "
|
||||
<< val.data << std::dec << " instead";
|
||||
}
|
||||
|
||||
if (expectedSpecFlags != specFlags) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected specFlags "
|
||||
<< std::hex << expectedSpecFlags << " but got specFlags "
|
||||
<< specFlags << std::dec << " instead";
|
||||
}
|
||||
|
||||
ResTable::resource_name actualName;
|
||||
if (!table->getResourceName(expectedId.id, false, &actualName)) {
|
||||
return ::testing::AssertionFailure() << "failed to find resource name";
|
||||
}
|
||||
|
||||
Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName);
|
||||
if (!resName) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected name '" << expectedResName << "' but got '"
|
||||
<< StringPiece16(actualName.package, actualName.packageLen)
|
||||
<< ":"
|
||||
<< StringPiece16(actualName.type, actualName.typeLen)
|
||||
<< "/"
|
||||
<< StringPiece16(actualName.name, actualName.nameLen)
|
||||
<< "'";
|
||||
}
|
||||
|
||||
if (expectedConfig != config) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected config '" << expectedConfig << "' but got '"
|
||||
<< ConfigDescription(config) << "'";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
if (expectedDataType != val.dataType) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data type " << std::hex << (int)expectedDataType
|
||||
<< " but got data type " << (int)val.dataType << std::dec
|
||||
<< " instead";
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
if (expectedData != val.data) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data " << std::hex << expectedData << " but got data "
|
||||
<< val.data << std::dec << " instead";
|
||||
}
|
||||
|
||||
if (expectedSpecFlags != specFlags) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected specFlags " << std::hex << expectedSpecFlags
|
||||
<< " but got specFlags " << specFlags << std::dec << " instead";
|
||||
}
|
||||
|
||||
ResTable::resource_name actualName;
|
||||
if (!table->getResourceName(expectedId.id, false, &actualName)) {
|
||||
return ::testing::AssertionFailure() << "failed to find resource name";
|
||||
}
|
||||
|
||||
Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName);
|
||||
if (!resName) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected name '" << expectedResName << "' but got '"
|
||||
<< StringPiece16(actualName.package, actualName.packageLen) << ":"
|
||||
<< StringPiece16(actualName.type, actualName.typeLen) << "/"
|
||||
<< StringPiece16(actualName.name, actualName.nameLen) << "'";
|
||||
}
|
||||
|
||||
if (expectedConfig != config) {
|
||||
return ::testing::AssertionFailure() << "expected config '"
|
||||
<< expectedConfig << "' but got '"
|
||||
<< ConfigDescription(config) << "'";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
};
|
||||
|
||||
TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId("com.app.test", 0x7f)
|
||||
.addSimple("com.app.test:id/one", ResourceId(0x7f020000))
|
||||
.addSimple("com.app.test:id/two", ResourceId(0x7f020001))
|
||||
.addValue("com.app.test:id/three", ResourceId(0x7f020002),
|
||||
test::buildReference("com.app.test:id/one", ResourceId(0x7f020000)))
|
||||
.addValue("com.app.test:integer/one", ResourceId(0x7f030000),
|
||||
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
|
||||
.addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"),
|
||||
ResourceId(0x7f030000),
|
||||
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
|
||||
.addString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
|
||||
.addString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.setPackageId("com.app.test", 0x7f)
|
||||
.addSimple("com.app.test:id/one", ResourceId(0x7f020000))
|
||||
.addSimple("com.app.test:id/two", ResourceId(0x7f020001))
|
||||
.addValue("com.app.test:id/three", ResourceId(0x7f020002),
|
||||
test::buildReference("com.app.test:id/one",
|
||||
ResourceId(0x7f020000)))
|
||||
.addValue("com.app.test:integer/one", ResourceId(0x7f030000),
|
||||
util::make_unique<BinaryPrimitive>(
|
||||
uint8_t(Res_value::TYPE_INT_DEC), 1u))
|
||||
.addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"),
|
||||
ResourceId(0x7f030000),
|
||||
util::make_unique<BinaryPrimitive>(
|
||||
uint8_t(Res_value::TYPE_INT_DEC), 2u))
|
||||
.addString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
|
||||
.addString("com.app.test:layout/bar", ResourceId(0x7f050000),
|
||||
"res/layout/bar.xml")
|
||||
.build();
|
||||
|
||||
ResTable resTable;
|
||||
ASSERT_TRUE(flatten(table.get(), &resTable));
|
||||
ResTable resTable;
|
||||
ASSERT_TRUE(flatten(table.get(), &resTable));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), {},
|
||||
Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000),
|
||||
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001), {},
|
||||
Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001),
|
||||
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002), {},
|
||||
Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002),
|
||||
{}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
|
||||
{}, Res_value::TYPE_INT_DEC, 1u,
|
||||
ResTable_config::CONFIG_VERSION));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one",
|
||||
ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
|
||||
ResTable_config::CONFIG_VERSION));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
|
||||
test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
|
||||
ResTable_config::CONFIG_VERSION));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one",
|
||||
ResourceId(0x7f030000), test::parseConfigOrDie("v1"),
|
||||
Res_value::TYPE_INT_DEC, 2u,
|
||||
ResTable_config::CONFIG_VERSION));
|
||||
|
||||
std::u16string fooStr = u"foo";
|
||||
ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:string/test", ResourceId(0x7f040000),
|
||||
{}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
|
||||
std::u16string fooStr = u"foo";
|
||||
ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(),
|
||||
fooStr.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:string/test",
|
||||
ResourceId(0x7f040000), {}, Res_value::TYPE_STRING,
|
||||
(uint32_t)idx, 0u));
|
||||
|
||||
std::u16string barPath = u"res/layout/bar.xml";
|
||||
idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
|
||||
Res_value::TYPE_STRING, (uint32_t) idx, 0u));
|
||||
std::u16string barPath = u"res/layout/bar.xml";
|
||||
idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(),
|
||||
barPath.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar",
|
||||
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING,
|
||||
(uint32_t)idx, 0u));
|
||||
}
|
||||
|
||||
TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId("com.app.test", 0x7f)
|
||||
.addSimple("com.app.test:id/one", ResourceId(0x7f020001))
|
||||
.addSimple("com.app.test:id/three", ResourceId(0x7f020003))
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.setPackageId("com.app.test", 0x7f)
|
||||
.addSimple("com.app.test:id/one", ResourceId(0x7f020001))
|
||||
.addSimple("com.app.test:id/three", ResourceId(0x7f020003))
|
||||
.build();
|
||||
|
||||
ResTable resTable;
|
||||
ASSERT_TRUE(flatten(table.get(), &resTable));
|
||||
ResTable resTable;
|
||||
ASSERT_TRUE(flatten(table.get(), &resTable));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001), {},
|
||||
Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003), {},
|
||||
Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001),
|
||||
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003),
|
||||
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
}
|
||||
|
||||
TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
|
||||
Attribute attr(false);
|
||||
attr.typeMask = android::ResTable_map::TYPE_INTEGER;
|
||||
attr.minInt = 10;
|
||||
attr.maxInt = 23;
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId("android", 0x01)
|
||||
.addValue("android:attr/foo", ResourceId(0x01010000),
|
||||
util::make_unique<Attribute>(attr))
|
||||
.build();
|
||||
Attribute attr(false);
|
||||
attr.typeMask = android::ResTable_map::TYPE_INTEGER;
|
||||
attr.minInt = 10;
|
||||
attr.maxInt = 23;
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.setPackageId("android", 0x01)
|
||||
.addValue("android:attr/foo", ResourceId(0x01010000),
|
||||
util::make_unique<Attribute>(attr))
|
||||
.build();
|
||||
|
||||
ResourceTable result;
|
||||
ASSERT_TRUE(flatten(table.get(), &result));
|
||||
ResourceTable result;
|
||||
ASSERT_TRUE(flatten(table.get(), &result));
|
||||
|
||||
Attribute* actualAttr = test::getValue<Attribute>(&result, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, actualAttr);
|
||||
EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
|
||||
EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
|
||||
EXPECT_EQ(attr.minInt, actualAttr->minInt);
|
||||
EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
|
||||
Attribute* actualAttr =
|
||||
test::getValue<Attribute>(&result, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, actualAttr);
|
||||
EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
|
||||
EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
|
||||
EXPECT_EQ(attr.minInt, actualAttr->minInt);
|
||||
EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "flatten/XmlFlattener.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "flatten/ChunkWriter.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "flatten/XmlFlattener.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <utils/misc.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <utils/misc.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace android;
|
||||
@@ -35,300 +35,316 @@ namespace {
|
||||
constexpr uint32_t kLowPriority = 0xffffffffu;
|
||||
|
||||
struct XmlFlattenerVisitor : public xml::Visitor {
|
||||
using xml::Visitor::visit;
|
||||
using xml::Visitor::visit;
|
||||
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
StringPool mPool;
|
||||
std::map<uint8_t, StringPool> mPackagePools;
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
StringPool mPool;
|
||||
std::map<uint8_t, StringPool> mPackagePools;
|
||||
|
||||
struct StringFlattenDest {
|
||||
StringPool::Ref ref;
|
||||
ResStringPool_ref* dest;
|
||||
};
|
||||
std::vector<StringFlattenDest> mStringRefs;
|
||||
struct StringFlattenDest {
|
||||
StringPool::Ref ref;
|
||||
ResStringPool_ref* dest;
|
||||
};
|
||||
std::vector<StringFlattenDest> mStringRefs;
|
||||
|
||||
// Scratch vector to filter attributes. We avoid allocations
|
||||
// making this a member.
|
||||
std::vector<xml::Attribute*> mFilteredAttrs;
|
||||
// Scratch vector to filter attributes. We avoid allocations
|
||||
// making this a member.
|
||||
std::vector<xml::Attribute*> mFilteredAttrs;
|
||||
|
||||
XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
|
||||
: mBuffer(buffer), mOptions(options) {}
|
||||
|
||||
XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
|
||||
mBuffer(buffer), mOptions(options) {
|
||||
void addString(const StringPiece& str, uint32_t priority,
|
||||
android::ResStringPool_ref* dest,
|
||||
bool treatEmptyStringAsNull = false) {
|
||||
if (str.empty() && treatEmptyStringAsNull) {
|
||||
// Some parts of the runtime treat null differently than empty string.
|
||||
dest->index = util::deviceToHost32(-1);
|
||||
} else {
|
||||
mStringRefs.push_back(StringFlattenDest{
|
||||
mPool.makeRef(str, StringPool::Context{priority}), dest});
|
||||
}
|
||||
}
|
||||
|
||||
void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
|
||||
mStringRefs.push_back(StringFlattenDest{ref, dest});
|
||||
}
|
||||
|
||||
void writeNamespace(xml::Namespace* node, uint16_t type) {
|
||||
ChunkWriter writer(mBuffer);
|
||||
|
||||
ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_namespaceExt* flatNs =
|
||||
writer.nextBlock<ResXMLTree_namespaceExt>();
|
||||
addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
|
||||
addString(node->namespaceUri, kLowPriority, &flatNs->uri);
|
||||
|
||||
writer.finish();
|
||||
}
|
||||
|
||||
void visit(xml::Namespace* node) override {
|
||||
if (node->namespaceUri == xml::kSchemaTools) {
|
||||
// Skip dedicated tools namespace.
|
||||
xml::Visitor::visit(node);
|
||||
} else {
|
||||
writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
|
||||
xml::Visitor::visit(node);
|
||||
writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(xml::Text* node) override {
|
||||
if (util::trimWhitespace(node->text).empty()) {
|
||||
// Skip whitespace only text nodes.
|
||||
return;
|
||||
}
|
||||
|
||||
void addString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
|
||||
bool treatEmptyStringAsNull = false) {
|
||||
if (str.empty() && treatEmptyStringAsNull) {
|
||||
// Some parts of the runtime treat null differently than empty string.
|
||||
dest->index = util::deviceToHost32(-1);
|
||||
} else {
|
||||
mStringRefs.push_back(StringFlattenDest{
|
||||
mPool.makeRef(str, StringPool::Context{ priority }),
|
||||
dest });
|
||||
}
|
||||
ChunkWriter writer(mBuffer);
|
||||
ResXMLTree_node* flatNode =
|
||||
writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
|
||||
addString(node->text, kLowPriority, &flatText->data);
|
||||
|
||||
writer.finish();
|
||||
}
|
||||
|
||||
void visit(xml::Element* node) override {
|
||||
{
|
||||
ChunkWriter startWriter(mBuffer);
|
||||
ResXMLTree_node* flatNode =
|
||||
startWriter.startChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_attrExt* flatElem =
|
||||
startWriter.nextBlock<ResXMLTree_attrExt>();
|
||||
|
||||
// A missing namespace must be null, not an empty string. Otherwise the
|
||||
// runtime
|
||||
// complains.
|
||||
addString(node->namespaceUri, kLowPriority, &flatElem->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
addString(node->name, kLowPriority, &flatElem->name,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
|
||||
flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
|
||||
flatElem->attributeSize =
|
||||
util::hostToDevice16(sizeof(ResXMLTree_attribute));
|
||||
|
||||
writeAttributes(node, flatElem, &startWriter);
|
||||
|
||||
startWriter.finish();
|
||||
}
|
||||
|
||||
void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
|
||||
mStringRefs.push_back(StringFlattenDest{ ref, dest });
|
||||
}
|
||||
|
||||
void writeNamespace(xml::Namespace* node, uint16_t type) {
|
||||
ChunkWriter writer(mBuffer);
|
||||
|
||||
ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
|
||||
addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
|
||||
addString(node->namespaceUri, kLowPriority, &flatNs->uri);
|
||||
|
||||
writer.finish();
|
||||
}
|
||||
|
||||
void visit(xml::Namespace* node) override {
|
||||
if (node->namespaceUri == xml::kSchemaTools) {
|
||||
// Skip dedicated tools namespace.
|
||||
xml::Visitor::visit(node);
|
||||
} else {
|
||||
writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
|
||||
xml::Visitor::visit(node);
|
||||
writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(xml::Text* node) override {
|
||||
if (util::trimWhitespace(node->text).empty()) {
|
||||
// Skip whitespace only text nodes.
|
||||
return;
|
||||
}
|
||||
|
||||
ChunkWriter writer(mBuffer);
|
||||
ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
|
||||
addString(node->text, kLowPriority, &flatText->data);
|
||||
|
||||
writer.finish();
|
||||
}
|
||||
|
||||
void visit(xml::Element* node) override {
|
||||
{
|
||||
ChunkWriter startWriter(mBuffer);
|
||||
ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
|
||||
RES_XML_START_ELEMENT_TYPE);
|
||||
flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
|
||||
|
||||
// A missing namespace must be null, not an empty string. Otherwise the runtime
|
||||
// complains.
|
||||
addString(node->namespaceUri, kLowPriority, &flatElem->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
addString(node->name, kLowPriority, &flatElem->name,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
|
||||
flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
|
||||
flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
|
||||
|
||||
writeAttributes(node, flatElem, &startWriter);
|
||||
|
||||
startWriter.finish();
|
||||
}
|
||||
|
||||
xml::Visitor::visit(node);
|
||||
|
||||
{
|
||||
ChunkWriter endWriter(mBuffer);
|
||||
ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
|
||||
RES_XML_END_ELEMENT_TYPE);
|
||||
flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatEndNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
|
||||
addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
addString(node->name, kLowPriority, &flatEndElem->name);
|
||||
|
||||
endWriter.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
|
||||
if (a->compiledAttribute && a->compiledAttribute.value().id) {
|
||||
if (b->compiledAttribute && b->compiledAttribute.value().id) {
|
||||
return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
|
||||
}
|
||||
return true;
|
||||
} else if (!b->compiledAttribute) {
|
||||
int diff = a->namespaceUri.compare(b->namespaceUri);
|
||||
if (diff < 0) {
|
||||
return true;
|
||||
} else if (diff > 0) {
|
||||
return false;
|
||||
}
|
||||
return a->name < b->name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
|
||||
mFilteredAttrs.clear();
|
||||
mFilteredAttrs.reserve(node->attributes.size());
|
||||
|
||||
// Filter the attributes.
|
||||
for (xml::Attribute& attr : node->attributes) {
|
||||
if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
|
||||
size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
|
||||
if (sdkLevel > mOptions.maxSdkLevel.value()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (attr.namespaceUri == xml::kSchemaTools) {
|
||||
continue;
|
||||
}
|
||||
mFilteredAttrs.push_back(&attr);
|
||||
}
|
||||
|
||||
if (mFilteredAttrs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResourceId kIdAttr(0x010100d0);
|
||||
|
||||
std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
|
||||
|
||||
flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
|
||||
|
||||
ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
|
||||
mFilteredAttrs.size());
|
||||
uint16_t attributeIndex = 1;
|
||||
for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
|
||||
// Assign the indices for specific attributes.
|
||||
if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
|
||||
xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
|
||||
flatElem->idIndex = util::hostToDevice16(attributeIndex);
|
||||
} else if (xmlAttr->namespaceUri.empty()) {
|
||||
if (xmlAttr->name == "class") {
|
||||
flatElem->classIndex = util::hostToDevice16(attributeIndex);
|
||||
} else if (xmlAttr->name == "style") {
|
||||
flatElem->styleIndex = util::hostToDevice16(attributeIndex);
|
||||
}
|
||||
}
|
||||
attributeIndex++;
|
||||
|
||||
// Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
|
||||
// is empty (doesn't exist).
|
||||
addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
|
||||
flatAttr->rawValue.index = util::hostToDevice32(-1);
|
||||
|
||||
if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
|
||||
// The attribute has no associated ResourceID, so the string order doesn't matter.
|
||||
addString(xmlAttr->name, kLowPriority, &flatAttr->name);
|
||||
} else {
|
||||
// Attribute names are stored without packages, but we use
|
||||
// their StringPool index to lookup their resource IDs.
|
||||
// This will cause collisions, so we can't dedupe
|
||||
// attribute names from different packages. We use separate
|
||||
// pools that we later combine.
|
||||
//
|
||||
// Lookup the StringPool for this package and make the reference there.
|
||||
const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
|
||||
|
||||
StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
|
||||
xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
|
||||
|
||||
// Add it to the list of strings to flatten.
|
||||
addString(nameRef, &flatAttr->name);
|
||||
}
|
||||
|
||||
if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
|
||||
// Keep raw values if the value is not compiled or
|
||||
// if we're building a static library (need symbols).
|
||||
addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
|
||||
}
|
||||
|
||||
if (xmlAttr->compiledValue) {
|
||||
bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
|
||||
assert(result);
|
||||
} else {
|
||||
// Flatten as a regular string type.
|
||||
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
addString(xmlAttr->value, kLowPriority,
|
||||
(ResStringPool_ref*) &flatAttr->typedValue.data);
|
||||
}
|
||||
|
||||
flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
|
||||
flatAttr++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
|
||||
BigBuffer nodeBuffer(1024);
|
||||
XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
|
||||
node->accept(&visitor);
|
||||
|
||||
// Merge the package pools into the main pool.
|
||||
for (auto& packagePoolEntry : visitor.mPackagePools) {
|
||||
visitor.mPool.merge(std::move(packagePoolEntry.second));
|
||||
}
|
||||
|
||||
// Sort the string pool so that attribute resource IDs show up first.
|
||||
visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.context.priority < b.context.priority;
|
||||
});
|
||||
|
||||
// Now we flatten the string pool references into the correct places.
|
||||
for (const auto& refEntry : visitor.mStringRefs) {
|
||||
refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
|
||||
}
|
||||
|
||||
// Write the XML header.
|
||||
ChunkWriter xmlHeaderWriter(mBuffer);
|
||||
xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
|
||||
|
||||
// Flatten the StringPool.
|
||||
StringPool::flattenUtf8(mBuffer, visitor.mPool);
|
||||
xml::Visitor::visit(node);
|
||||
|
||||
{
|
||||
// Write the array of resource IDs, indexed by StringPool order.
|
||||
ChunkWriter resIdMapWriter(mBuffer);
|
||||
resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
|
||||
for (const auto& str : visitor.mPool) {
|
||||
ResourceId id = { str->context.priority };
|
||||
if (id.id == kLowPriority || !id.isValid()) {
|
||||
// When we see the first non-resource ID,
|
||||
// we're done.
|
||||
break;
|
||||
}
|
||||
ChunkWriter endWriter(mBuffer);
|
||||
ResXMLTree_node* flatEndNode =
|
||||
endWriter.startChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
|
||||
flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
|
||||
flatEndNode->comment.index = util::hostToDevice32(-1);
|
||||
|
||||
*resIdMapWriter.nextBlock<uint32_t>() = id.id;
|
||||
ResXMLTree_endElementExt* flatEndElem =
|
||||
endWriter.nextBlock<ResXMLTree_endElementExt>();
|
||||
addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
addString(node->name, kLowPriority, &flatEndElem->name);
|
||||
|
||||
endWriter.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static bool cmpXmlAttributeById(const xml::Attribute* a,
|
||||
const xml::Attribute* b) {
|
||||
if (a->compiledAttribute && a->compiledAttribute.value().id) {
|
||||
if (b->compiledAttribute && b->compiledAttribute.value().id) {
|
||||
return a->compiledAttribute.value().id.value() <
|
||||
b->compiledAttribute.value().id.value();
|
||||
}
|
||||
return true;
|
||||
} else if (!b->compiledAttribute) {
|
||||
int diff = a->namespaceUri.compare(b->namespaceUri);
|
||||
if (diff < 0) {
|
||||
return true;
|
||||
} else if (diff > 0) {
|
||||
return false;
|
||||
}
|
||||
return a->name < b->name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem,
|
||||
ChunkWriter* writer) {
|
||||
mFilteredAttrs.clear();
|
||||
mFilteredAttrs.reserve(node->attributes.size());
|
||||
|
||||
// Filter the attributes.
|
||||
for (xml::Attribute& attr : node->attributes) {
|
||||
if (mOptions.maxSdkLevel && attr.compiledAttribute &&
|
||||
attr.compiledAttribute.value().id) {
|
||||
size_t sdkLevel =
|
||||
findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
|
||||
if (sdkLevel > mOptions.maxSdkLevel.value()) {
|
||||
continue;
|
||||
}
|
||||
resIdMapWriter.finish();
|
||||
}
|
||||
if (attr.namespaceUri == xml::kSchemaTools) {
|
||||
continue;
|
||||
}
|
||||
mFilteredAttrs.push_back(&attr);
|
||||
}
|
||||
|
||||
// Move the nodeBuffer and append it to the out buffer.
|
||||
mBuffer->appendBuffer(std::move(nodeBuffer));
|
||||
if (mFilteredAttrs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finish the xml header.
|
||||
xmlHeaderWriter.finish();
|
||||
return true;
|
||||
const ResourceId kIdAttr(0x010100d0);
|
||||
|
||||
std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(),
|
||||
cmpXmlAttributeById);
|
||||
|
||||
flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
|
||||
|
||||
ResXMLTree_attribute* flatAttr =
|
||||
writer->nextBlock<ResXMLTree_attribute>(mFilteredAttrs.size());
|
||||
uint16_t attributeIndex = 1;
|
||||
for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
|
||||
// Assign the indices for specific attributes.
|
||||
if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
|
||||
xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
|
||||
flatElem->idIndex = util::hostToDevice16(attributeIndex);
|
||||
} else if (xmlAttr->namespaceUri.empty()) {
|
||||
if (xmlAttr->name == "class") {
|
||||
flatElem->classIndex = util::hostToDevice16(attributeIndex);
|
||||
} else if (xmlAttr->name == "style") {
|
||||
flatElem->styleIndex = util::hostToDevice16(attributeIndex);
|
||||
}
|
||||
}
|
||||
attributeIndex++;
|
||||
|
||||
// Add the namespaceUri to the list of StringRefs to encode. Use null if
|
||||
// the namespace
|
||||
// is empty (doesn't exist).
|
||||
addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
|
||||
true /* treatEmptyStringAsNull */);
|
||||
|
||||
flatAttr->rawValue.index = util::hostToDevice32(-1);
|
||||
|
||||
if (!xmlAttr->compiledAttribute ||
|
||||
!xmlAttr->compiledAttribute.value().id) {
|
||||
// The attribute has no associated ResourceID, so the string order
|
||||
// doesn't matter.
|
||||
addString(xmlAttr->name, kLowPriority, &flatAttr->name);
|
||||
} else {
|
||||
// Attribute names are stored without packages, but we use
|
||||
// their StringPool index to lookup their resource IDs.
|
||||
// This will cause collisions, so we can't dedupe
|
||||
// attribute names from different packages. We use separate
|
||||
// pools that we later combine.
|
||||
//
|
||||
// Lookup the StringPool for this package and make the reference there.
|
||||
const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
|
||||
|
||||
StringPool::Ref nameRef =
|
||||
mPackagePools[aaptAttr.id.value().packageId()].makeRef(
|
||||
xmlAttr->name, StringPool::Context{aaptAttr.id.value().id});
|
||||
|
||||
// Add it to the list of strings to flatten.
|
||||
addString(nameRef, &flatAttr->name);
|
||||
}
|
||||
|
||||
if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
|
||||
// Keep raw values if the value is not compiled or
|
||||
// if we're building a static library (need symbols).
|
||||
addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
|
||||
}
|
||||
|
||||
if (xmlAttr->compiledValue) {
|
||||
bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
|
||||
assert(result);
|
||||
} else {
|
||||
// Flatten as a regular string type.
|
||||
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
addString(xmlAttr->value, kLowPriority,
|
||||
(ResStringPool_ref*)&flatAttr->typedValue.data);
|
||||
}
|
||||
|
||||
flatAttr->typedValue.size =
|
||||
util::hostToDevice16(sizeof(flatAttr->typedValue));
|
||||
flatAttr++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
|
||||
BigBuffer nodeBuffer(1024);
|
||||
XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
|
||||
node->accept(&visitor);
|
||||
|
||||
// Merge the package pools into the main pool.
|
||||
for (auto& packagePoolEntry : visitor.mPackagePools) {
|
||||
visitor.mPool.merge(std::move(packagePoolEntry.second));
|
||||
}
|
||||
|
||||
// Sort the string pool so that attribute resource IDs show up first.
|
||||
visitor.mPool.sort(
|
||||
[](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.context.priority < b.context.priority;
|
||||
});
|
||||
|
||||
// Now we flatten the string pool references into the correct places.
|
||||
for (const auto& refEntry : visitor.mStringRefs) {
|
||||
refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
|
||||
}
|
||||
|
||||
// Write the XML header.
|
||||
ChunkWriter xmlHeaderWriter(mBuffer);
|
||||
xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
|
||||
|
||||
// Flatten the StringPool.
|
||||
StringPool::flattenUtf8(mBuffer, visitor.mPool);
|
||||
|
||||
{
|
||||
// Write the array of resource IDs, indexed by StringPool order.
|
||||
ChunkWriter resIdMapWriter(mBuffer);
|
||||
resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
|
||||
for (const auto& str : visitor.mPool) {
|
||||
ResourceId id = {str->context.priority};
|
||||
if (id.id == kLowPriority || !id.isValid()) {
|
||||
// When we see the first non-resource ID,
|
||||
// we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
*resIdMapWriter.nextBlock<uint32_t>() = id.id;
|
||||
}
|
||||
resIdMapWriter.finish();
|
||||
}
|
||||
|
||||
// Move the nodeBuffer and append it to the out buffer.
|
||||
mBuffer->appendBuffer(std::move(nodeBuffer));
|
||||
|
||||
// Finish the xml header.
|
||||
xmlHeaderWriter.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
|
||||
if (!resource->root) {
|
||||
return false;
|
||||
}
|
||||
return flatten(context, resource->root.get());
|
||||
if (!resource->root) {
|
||||
return false;
|
||||
}
|
||||
return flatten(context, resource->root.get());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -24,32 +24,31 @@
|
||||
namespace aapt {
|
||||
|
||||
struct XmlFlattenerOptions {
|
||||
/**
|
||||
* Keep attribute raw string values along with typed values.
|
||||
*/
|
||||
bool keepRawValues = false;
|
||||
/**
|
||||
* Keep attribute raw string values along with typed values.
|
||||
*/
|
||||
bool keepRawValues = false;
|
||||
|
||||
/**
|
||||
* If set, the max SDK level of attribute to flatten. All others are ignored.
|
||||
*/
|
||||
Maybe<size_t> maxSdkLevel;
|
||||
/**
|
||||
* If set, the max SDK level of attribute to flatten. All others are ignored.
|
||||
*/
|
||||
Maybe<size_t> maxSdkLevel;
|
||||
};
|
||||
|
||||
class XmlFlattener : public IXmlResourceConsumer {
|
||||
public:
|
||||
XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) :
|
||||
mBuffer(buffer), mOptions(options) {
|
||||
}
|
||||
public:
|
||||
XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
|
||||
: mBuffer(buffer), mOptions(options) {}
|
||||
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
|
||||
bool flatten(IAaptContext* context, xml::Node* node);
|
||||
bool flatten(IAaptContext* context, xml::Node* node);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
|
||||
|
||||
@@ -25,230 +25,240 @@
|
||||
namespace aapt {
|
||||
|
||||
class XmlFlattenerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
|
||||
.addSymbolSource(test::StaticSymbolSourceBuilder()
|
||||
.addSymbol("android:attr/id", ResourceId(0x010100d0),
|
||||
test::AttributeBuilder().build())
|
||||
.addSymbol("com.app.test:id/id", ResourceId(0x7f020000))
|
||||
.addSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
|
||||
test::AttributeBuilder().build())
|
||||
.addSymbol("android:attr/colorAccent", ResourceId(0x01010435),
|
||||
test::AttributeBuilder().build())
|
||||
.build())
|
||||
.build();
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext =
|
||||
test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
|
||||
.addSymbolSource(
|
||||
test::StaticSymbolSourceBuilder()
|
||||
.addSymbol("android:attr/id", ResourceId(0x010100d0),
|
||||
test::AttributeBuilder().build())
|
||||
.addSymbol("com.app.test:id/id", ResourceId(0x7f020000))
|
||||
.addSymbol("android:attr/paddingStart",
|
||||
ResourceId(0x010103b3),
|
||||
test::AttributeBuilder().build())
|
||||
.addSymbol("android:attr/colorAccent",
|
||||
ResourceId(0x01010435),
|
||||
test::AttributeBuilder().build())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(xml::XmlResource* doc,
|
||||
android::ResXMLTree* outTree,
|
||||
const XmlFlattenerOptions& options = {}) {
|
||||
using namespace android; // For NO_ERROR on windows because it is a macro.
|
||||
|
||||
BigBuffer buffer(1024);
|
||||
XmlFlattener flattener(&buffer, options);
|
||||
if (!flattener.consume(mContext.get(), doc)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten XML Tree";
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
|
||||
const XmlFlattenerOptions& options = {}) {
|
||||
using namespace android; // For NO_ERROR on windows because it is a macro.
|
||||
|
||||
BigBuffer buffer(1024);
|
||||
XmlFlattener flattener(&buffer, options);
|
||||
if (!flattener.consume(mContext.get(), doc)) {
|
||||
return ::testing::AssertionFailure() << "failed to flatten XML Tree";
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure() << "flattened XML is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure() << "flattened XML is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
protected:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
};
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:test="http://com.test"
|
||||
attr="hey">
|
||||
<Layout test:hello="hi" />
|
||||
<Layout>Some text</Layout>
|
||||
</View>)EOF");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
|
||||
size_t len;
|
||||
const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
|
||||
|
||||
size_t len;
|
||||
const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
const char16_t* tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"View");
|
||||
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
const char16_t* tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"View");
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
|
||||
const char16_t* attrName = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrName, len), u"attr");
|
||||
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr);
|
||||
const char16_t* attrName = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrName, len), u"attr");
|
||||
EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr",
|
||||
StringPiece16(u"attr").size()));
|
||||
|
||||
EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
|
||||
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test");
|
||||
attrName = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrName, len), u"hello");
|
||||
|
||||
attrName = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrName, len), u"hello");
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
ASSERT_EQ(0u, tree.getAttributeCount());
|
||||
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
ASSERT_EQ(0u, tree.getAttributeCount());
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
|
||||
const char16_t* text = tree.getText(&len);
|
||||
EXPECT_EQ(StringPiece16(text, len), u"Some text");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
|
||||
const char16_t* text = tree.getText(&len);
|
||||
EXPECT_EQ(StringPiece16(text, len), u"Some text");
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"Layout");
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"View");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
|
||||
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
|
||||
tagName = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tagName, len), u"View");
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
|
||||
namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
|
||||
namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
|
||||
namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
|
||||
namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:paddingStart="1dp"
|
||||
android:colorAccent="#ffffff"/>)EOF");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
|
||||
ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
|
||||
ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
|
||||
XmlReferenceLinker linker;
|
||||
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
|
||||
ASSERT_TRUE(linker.getSdkLevels().count(17) == 1);
|
||||
ASSERT_TRUE(linker.getSdkLevels().count(21) == 1);
|
||||
|
||||
android::ResXMLTree tree;
|
||||
XmlFlattenerOptions options;
|
||||
options.maxSdkLevel = 17;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree, options));
|
||||
android::ResXMLTree tree;
|
||||
XmlFlattenerOptions options;
|
||||
options.maxSdkLevel = 17;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree, options));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
|
||||
ASSERT_EQ(1u, tree.getAttributeCount());
|
||||
EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:foo="http://schemas.android.com/foo"
|
||||
foo:bar="Foo"
|
||||
tools:ignore="MissingTranslation"/>)EOF");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
|
||||
|
||||
size_t len;
|
||||
const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo");
|
||||
size_t len;
|
||||
const char16_t* namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo");
|
||||
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://schemas.android.com/foo");
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len),
|
||||
u"http://schemas.android.com/foo");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
|
||||
EXPECT_EQ(
|
||||
tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
|
||||
EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
|
||||
android::NAME_NOT_FOUND);
|
||||
EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
|
||||
EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@id/id"
|
||||
class="str"
|
||||
style="@id/id"/>)EOF");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
EXPECT_EQ(tree.indexOfClass(), 0);
|
||||
EXPECT_EQ(tree.indexOfStyle(), 1);
|
||||
EXPECT_EQ(tree.indexOfClass(), 0);
|
||||
EXPECT_EQ(tree.indexOfStyle(), 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* The device ResXMLParser in libandroidfw differentiates between empty namespace and null
|
||||
* The device ResXMLParser in libandroidfw differentiates between empty
|
||||
* namespace and null
|
||||
* namespace.
|
||||
*/
|
||||
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
test::buildXmlDom("<View package=\"android\"/>");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
const StringPiece16 kPackage = u"package";
|
||||
EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
|
||||
const StringPiece16 kPackage = u"package";
|
||||
EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()),
|
||||
0);
|
||||
}
|
||||
|
||||
TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"\"/>");
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
test::buildXmlDom("<View package=\"\"/>");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
|
||||
}
|
||||
|
||||
const StringPiece16 kPackage = u"package";
|
||||
ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
const StringPiece16 kPackage = u"package";
|
||||
ssize_t idx =
|
||||
tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
|
||||
size_t len;
|
||||
EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
|
||||
size_t len;
|
||||
EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -18,105 +18,95 @@
|
||||
#define AAPT_IO_DATA_H
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <memory>
|
||||
#include <utils/FileMap.h>
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
/**
|
||||
* Interface for a block of contiguous memory. An instance of this interface owns the data.
|
||||
* Interface for a block of contiguous memory. An instance of this interface
|
||||
* owns the data.
|
||||
*/
|
||||
class IData {
|
||||
public:
|
||||
virtual ~IData() = default;
|
||||
public:
|
||||
virtual ~IData() = default;
|
||||
|
||||
virtual const void* data() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
virtual const void* data() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
};
|
||||
|
||||
class DataSegment : public IData {
|
||||
public:
|
||||
explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) :
|
||||
mData(std::move(data)), mOffset(offset), mLen(len) {
|
||||
}
|
||||
public:
|
||||
explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
|
||||
: mData(std::move(data)), mOffset(offset), mLen(len) {}
|
||||
|
||||
const void* data() const override {
|
||||
return static_cast<const uint8_t*>(mData->data()) + mOffset;
|
||||
}
|
||||
const void* data() const override {
|
||||
return static_cast<const uint8_t*>(mData->data()) + mOffset;
|
||||
}
|
||||
|
||||
size_t size() const override {
|
||||
return mLen;
|
||||
}
|
||||
size_t size() const override { return mLen; }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DataSegment);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DataSegment);
|
||||
|
||||
std::unique_ptr<IData> mData;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
std::unique_ptr<IData> mData;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
|
||||
* Implementation of IData that exposes a memory mapped file. The mmapped file
|
||||
* is owned by this
|
||||
* object.
|
||||
*/
|
||||
class MmappedData : public IData {
|
||||
public:
|
||||
explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
|
||||
}
|
||||
public:
|
||||
explicit MmappedData(android::FileMap&& map)
|
||||
: mMap(std::forward<android::FileMap>(map)) {}
|
||||
|
||||
const void* data() const override {
|
||||
return mMap.getDataPtr();
|
||||
}
|
||||
const void* data() const override { return mMap.getDataPtr(); }
|
||||
|
||||
size_t size() const override {
|
||||
return mMap.getDataLength();
|
||||
}
|
||||
size_t size() const override { return mMap.getDataLength(); }
|
||||
|
||||
private:
|
||||
android::FileMap mMap;
|
||||
private:
|
||||
android::FileMap mMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
|
||||
* Implementation of IData that exposes a block of memory that was malloc'ed
|
||||
* (new'ed). The
|
||||
* memory is owned by this object.
|
||||
*/
|
||||
class MallocData : public IData {
|
||||
public:
|
||||
MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
|
||||
mData(std::move(data)), mSize(size) {
|
||||
}
|
||||
public:
|
||||
MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
|
||||
: mData(std::move(data)), mSize(size) {}
|
||||
|
||||
const void* data() const override {
|
||||
return mData.get();
|
||||
}
|
||||
const void* data() const override { return mData.get(); }
|
||||
|
||||
size_t size() const override {
|
||||
return mSize;
|
||||
}
|
||||
size_t size() const override { return mSize; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<const uint8_t[]> mData;
|
||||
size_t mSize;
|
||||
private:
|
||||
std::unique_ptr<const uint8_t[]> mData;
|
||||
size_t mSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
|
||||
* When mmap fails because the file has length 0, we use the EmptyData to
|
||||
* simulate data of length 0.
|
||||
*/
|
||||
class EmptyData : public IData {
|
||||
public:
|
||||
const void* data() const override {
|
||||
static const uint8_t d = 0;
|
||||
return &d;
|
||||
}
|
||||
public:
|
||||
const void* data() const override {
|
||||
static const uint8_t d = 0;
|
||||
return &d;
|
||||
}
|
||||
|
||||
size_t size() const override {
|
||||
return 0u;
|
||||
}
|
||||
size_t size() const override { return 0u; }
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_IO_DATA_H */
|
||||
|
||||
@@ -30,83 +30,90 @@ namespace aapt {
|
||||
namespace io {
|
||||
|
||||
/**
|
||||
* Interface for a file, which could be a real file on the file system, or a file inside
|
||||
* Interface for a file, which could be a real file on the file system, or a
|
||||
* file inside
|
||||
* a ZIP archive.
|
||||
*/
|
||||
class IFile {
|
||||
public:
|
||||
virtual ~IFile() = default;
|
||||
public:
|
||||
virtual ~IFile() = default;
|
||||
|
||||
/**
|
||||
* Open the file and return it as a block of contiguous memory. How this occurs is
|
||||
* implementation dependent. For example, if this is a file on the file system, it may
|
||||
* simply mmap the contents. If this file represents a compressed file in a ZIP archive,
|
||||
* it may need to inflate it to memory, incurring a copy.
|
||||
*
|
||||
* Returns nullptr on failure.
|
||||
*/
|
||||
virtual std::unique_ptr<IData> openAsData() = 0;
|
||||
/**
|
||||
* Open the file and return it as a block of contiguous memory. How this
|
||||
* occurs is
|
||||
* implementation dependent. For example, if this is a file on the file
|
||||
* system, it may
|
||||
* simply mmap the contents. If this file represents a compressed file in a
|
||||
* ZIP archive,
|
||||
* it may need to inflate it to memory, incurring a copy.
|
||||
*
|
||||
* Returns nullptr on failure.
|
||||
*/
|
||||
virtual std::unique_ptr<IData> openAsData() = 0;
|
||||
|
||||
/**
|
||||
* Returns the source of this file. This is for presentation to the user and may not be a
|
||||
* valid file system path (for example, it may contain a '@' sign to separate the files within
|
||||
* a ZIP archive from the path to the containing ZIP archive.
|
||||
*/
|
||||
virtual const Source& getSource() const = 0;
|
||||
/**
|
||||
* Returns the source of this file. This is for presentation to the user and
|
||||
* may not be a
|
||||
* valid file system path (for example, it may contain a '@' sign to separate
|
||||
* the files within
|
||||
* a ZIP archive from the path to the containing ZIP archive.
|
||||
*/
|
||||
virtual const Source& getSource() const = 0;
|
||||
|
||||
IFile* createFileSegment(size_t offset, size_t len);
|
||||
IFile* createFileSegment(size_t offset, size_t len);
|
||||
|
||||
private:
|
||||
// Any segments created from this IFile need to be owned by this IFile, so keep them
|
||||
// in a list. This will never be read, so we prefer better insertion performance
|
||||
// than cache locality, hence the list.
|
||||
std::list<std::unique_ptr<IFile>> mSegments;
|
||||
private:
|
||||
// Any segments created from this IFile need to be owned by this IFile, so
|
||||
// keep them
|
||||
// in a list. This will never be read, so we prefer better insertion
|
||||
// performance
|
||||
// than cache locality, hence the list.
|
||||
std::list<std::unique_ptr<IFile>> mSegments;
|
||||
};
|
||||
|
||||
/**
|
||||
* An IFile that wraps an underlying IFile but limits it to a subsection of that file.
|
||||
* An IFile that wraps an underlying IFile but limits it to a subsection of that
|
||||
* file.
|
||||
*/
|
||||
class FileSegment : public IFile {
|
||||
public:
|
||||
explicit FileSegment(IFile* file, size_t offset, size_t len) :
|
||||
mFile(file), mOffset(offset), mLen(len) {
|
||||
}
|
||||
public:
|
||||
explicit FileSegment(IFile* file, size_t offset, size_t len)
|
||||
: mFile(file), mOffset(offset), mLen(len) {}
|
||||
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
|
||||
const Source& getSource() const override {
|
||||
return mFile->getSource();
|
||||
}
|
||||
const Source& getSource() const override { return mFile->getSource(); }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FileSegment);
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FileSegment);
|
||||
|
||||
IFile* mFile;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
IFile* mFile;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
};
|
||||
|
||||
class IFileCollectionIterator {
|
||||
public:
|
||||
virtual ~IFileCollectionIterator() = default;
|
||||
public:
|
||||
virtual ~IFileCollectionIterator() = default;
|
||||
|
||||
virtual bool hasNext() = 0;
|
||||
virtual IFile* next() = 0;
|
||||
virtual bool hasNext() = 0;
|
||||
virtual IFile* next() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for a collection of files, all of which share a common source. That source may
|
||||
* Interface for a collection of files, all of which share a common source. That
|
||||
* source may
|
||||
* simply be the filesystem, or a ZIP archive.
|
||||
*/
|
||||
class IFileCollection {
|
||||
public:
|
||||
virtual ~IFileCollection() = default;
|
||||
public:
|
||||
virtual ~IFileCollection() = default;
|
||||
|
||||
virtual IFile* findFile(const StringPiece& path) = 0;
|
||||
virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
|
||||
virtual IFile* findFile(const StringPiece& path) = 0;
|
||||
virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0;
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_IO_FILE_H */
|
||||
|
||||
@@ -28,47 +28,47 @@ namespace io {
|
||||
* A regular file from the file system. Uses mmap to open the data.
|
||||
*/
|
||||
class RegularFile : public IFile {
|
||||
public:
|
||||
explicit RegularFile(const Source& source);
|
||||
public:
|
||||
explicit RegularFile(const Source& source);
|
||||
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
const Source& getSource() const override;
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
const Source& getSource() const override;
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
private:
|
||||
Source mSource;
|
||||
};
|
||||
|
||||
class FileCollection;
|
||||
|
||||
class FileCollectionIterator : public IFileCollectionIterator {
|
||||
public:
|
||||
explicit FileCollectionIterator(FileCollection* collection);
|
||||
public:
|
||||
explicit FileCollectionIterator(FileCollection* collection);
|
||||
|
||||
bool hasNext() override;
|
||||
io::IFile* next() override;
|
||||
bool hasNext() override;
|
||||
io::IFile* next() override;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* An IFileCollection representing the file system.
|
||||
*/
|
||||
class FileCollection : public IFileCollection {
|
||||
public:
|
||||
/**
|
||||
* Adds a file located at path. Returns the IFile representation of that file.
|
||||
*/
|
||||
IFile* insertFile(const StringPiece& path);
|
||||
IFile* findFile(const StringPiece& path) override;
|
||||
std::unique_ptr<IFileCollectionIterator> iterator() override;
|
||||
public:
|
||||
/**
|
||||
* Adds a file located at path. Returns the IFile representation of that file.
|
||||
*/
|
||||
IFile* insertFile(const StringPiece& path);
|
||||
IFile* findFile(const StringPiece& path) override;
|
||||
std::unique_ptr<IFileCollectionIterator> iterator() override;
|
||||
|
||||
private:
|
||||
friend class FileCollectionIterator;
|
||||
std::map<std::string, std::unique_ptr<IFile>> mFiles;
|
||||
private:
|
||||
friend class FileCollectionIterator;
|
||||
std::map<std::string, std::unique_ptr<IFile>> mFiles;
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_IO_FILESYSTEM_H
|
||||
#endif // AAPT_IO_FILESYSTEM_H
|
||||
|
||||
@@ -30,12 +30,10 @@ namespace io {
|
||||
* The code style here matches the protobuf style.
|
||||
*/
|
||||
class InputStream : public google::protobuf::io::ZeroCopyInputStream {
|
||||
public:
|
||||
virtual std::string GetError() const {
|
||||
return {};
|
||||
}
|
||||
public:
|
||||
virtual std::string GetError() const { return {}; }
|
||||
|
||||
virtual bool HadError() const = 0;
|
||||
virtual bool HadError() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -45,12 +43,10 @@ public:
|
||||
* The code style here matches the protobuf style.
|
||||
*/
|
||||
class OutputStream : public google::protobuf::io::ZeroCopyOutputStream {
|
||||
public:
|
||||
virtual std::string GetError() const {
|
||||
return {};
|
||||
}
|
||||
public:
|
||||
virtual std::string GetError() const { return {}; }
|
||||
|
||||
virtual bool HadError() const = 0;
|
||||
virtual bool HadError() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,7 +56,7 @@ public:
|
||||
*/
|
||||
bool copy(OutputStream* out, InputStream* in);
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_IO_IO_H */
|
||||
|
||||
@@ -20,64 +20,66 @@
|
||||
#include "io/File.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <map>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
#include <map>
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
/**
|
||||
* An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
|
||||
* and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
|
||||
* An IFile representing a file within a ZIP archive. If the file is compressed,
|
||||
* it is uncompressed
|
||||
* and copied into memory when opened. Otherwise it is mmapped from the ZIP
|
||||
* archive.
|
||||
*/
|
||||
class ZipFile : public IFile {
|
||||
public:
|
||||
ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
|
||||
public:
|
||||
ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
|
||||
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
const Source& getSource() const override;
|
||||
std::unique_ptr<IData> openAsData() override;
|
||||
const Source& getSource() const override;
|
||||
|
||||
private:
|
||||
ZipArchiveHandle mZipHandle;
|
||||
ZipEntry mZipEntry;
|
||||
Source mSource;
|
||||
private:
|
||||
ZipArchiveHandle mZipHandle;
|
||||
ZipEntry mZipEntry;
|
||||
Source mSource;
|
||||
};
|
||||
|
||||
class ZipFileCollection;
|
||||
|
||||
class ZipFileCollectionIterator : public IFileCollectionIterator {
|
||||
public:
|
||||
explicit ZipFileCollectionIterator(ZipFileCollection* collection);
|
||||
public:
|
||||
explicit ZipFileCollectionIterator(ZipFileCollection* collection);
|
||||
|
||||
bool hasNext() override;
|
||||
io::IFile* next() override;
|
||||
bool hasNext() override;
|
||||
io::IFile* next() override;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* An IFileCollection that represents a ZIP archive and the entries within it.
|
||||
*/
|
||||
class ZipFileCollection : public IFileCollection {
|
||||
public:
|
||||
static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
|
||||
std::string* outError);
|
||||
public:
|
||||
static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
|
||||
std::string* outError);
|
||||
|
||||
io::IFile* findFile(const StringPiece& path) override;
|
||||
std::unique_ptr<IFileCollectionIterator> iterator() override;
|
||||
io::IFile* findFile(const StringPiece& path) override;
|
||||
std::unique_ptr<IFileCollectionIterator> iterator() override;
|
||||
|
||||
~ZipFileCollection() override;
|
||||
~ZipFileCollection() override;
|
||||
|
||||
private:
|
||||
friend class ZipFileCollectionIterator;
|
||||
ZipFileCollection();
|
||||
private:
|
||||
friend class ZipFileCollectionIterator;
|
||||
ZipFileCollection();
|
||||
|
||||
ZipArchiveHandle mHandle;
|
||||
std::map<std::string, std::unique_ptr<IFile>> mFiles;
|
||||
ZipArchiveHandle mHandle;
|
||||
std::map<std::string, std::unique_ptr<IFile>> mFiles;
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_IO_ZIPARCHIVE_H */
|
||||
|
||||
@@ -52,34 +52,36 @@ namespace aapt {
|
||||
*
|
||||
*/
|
||||
class AnnotationProcessor {
|
||||
public:
|
||||
/**
|
||||
* Adds more comments. Since resources can have various values with different configurations,
|
||||
* we need to collect all the comments.
|
||||
*/
|
||||
void appendComment(const StringPiece& comment);
|
||||
public:
|
||||
/**
|
||||
* Adds more comments. Since resources can have various values with different
|
||||
* configurations,
|
||||
* we need to collect all the comments.
|
||||
*/
|
||||
void appendComment(const StringPiece& comment);
|
||||
|
||||
void appendNewLine();
|
||||
void appendNewLine();
|
||||
|
||||
/**
|
||||
* Writes the comments and annotations to the stream, with the given prefix before each line.
|
||||
*/
|
||||
void writeToStream(std::ostream* out, const StringPiece& prefix) const;
|
||||
/**
|
||||
* Writes the comments and annotations to the stream, with the given prefix
|
||||
* before each line.
|
||||
*/
|
||||
void writeToStream(std::ostream* out, const StringPiece& prefix) const;
|
||||
|
||||
private:
|
||||
enum : uint32_t {
|
||||
kDeprecated = 0x01,
|
||||
kSystemApi = 0x02,
|
||||
};
|
||||
private:
|
||||
enum : uint32_t {
|
||||
kDeprecated = 0x01,
|
||||
kSystemApi = 0x02,
|
||||
};
|
||||
|
||||
std::stringstream mComment;
|
||||
std::stringstream mAnnotations;
|
||||
bool mHasComments = false;
|
||||
uint32_t mAnnotationBitMask = 0;
|
||||
std::stringstream mComment;
|
||||
std::stringstream mAnnotations;
|
||||
bool mHasComments = false;
|
||||
uint32_t mAnnotationBitMask = 0;
|
||||
|
||||
void appendCommentLine(std::string& line);
|
||||
void appendCommentLine(std::string& line);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
|
||||
|
||||
@@ -33,46 +33,43 @@ constexpr static size_t kAttribsPerLine = 4;
|
||||
constexpr static const char* kIndent = " ";
|
||||
|
||||
class ClassMember {
|
||||
public:
|
||||
virtual ~ClassMember() = default;
|
||||
public:
|
||||
virtual ~ClassMember() = default;
|
||||
|
||||
AnnotationProcessor* getCommentBuilder() {
|
||||
return &mProcessor;
|
||||
}
|
||||
AnnotationProcessor* getCommentBuilder() { return &mProcessor; }
|
||||
|
||||
virtual bool empty() const = 0;
|
||||
virtual bool empty() const = 0;
|
||||
|
||||
virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
|
||||
mProcessor.writeToStream(out, prefix);
|
||||
}
|
||||
virtual void writeToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const {
|
||||
mProcessor.writeToStream(out, prefix);
|
||||
}
|
||||
|
||||
private:
|
||||
AnnotationProcessor mProcessor;
|
||||
private:
|
||||
AnnotationProcessor mProcessor;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class PrimitiveMember : public ClassMember {
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const T& val) :
|
||||
mName(name.toString()), mVal(val) {
|
||||
}
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const T& val)
|
||||
: mName(name.toString()), mVal(val) {}
|
||||
|
||||
bool empty() const override {
|
||||
return false;
|
||||
}
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static " << (final ? "final " : "")
|
||||
<< "int " << mName << "=" << mVal << ";";
|
||||
}
|
||||
*out << prefix << "public static " << (final ? "final " : "") << "int "
|
||||
<< mName << "=" << mVal << ";";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
T mVal;
|
||||
private:
|
||||
std::string mName;
|
||||
T mVal;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,27 +77,25 @@ private:
|
||||
*/
|
||||
template <>
|
||||
class PrimitiveMember<std::string> : public ClassMember {
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const std::string& val) :
|
||||
mName(name.toString()), mVal(val) {
|
||||
}
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const std::string& val)
|
||||
: mName(name.toString()), mVal(val) {}
|
||||
|
||||
bool empty() const override {
|
||||
return false;
|
||||
}
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static " << (final ? "final " : "")
|
||||
<< "String " << mName << "=\"" << mVal << "\";";
|
||||
}
|
||||
*out << prefix << "public static " << (final ? "final " : "") << "String "
|
||||
<< mName << "=\"" << mVal << "\";";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mVal;
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mVal;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
};
|
||||
|
||||
using IntMember = PrimitiveMember<uint32_t>;
|
||||
@@ -109,80 +104,75 @@ using StringMember = PrimitiveMember<std::string>;
|
||||
|
||||
template <typename T>
|
||||
class PrimitiveArrayMember : public ClassMember {
|
||||
public:
|
||||
explicit PrimitiveArrayMember(const StringPiece& name) :
|
||||
mName(name.toString()) {
|
||||
public:
|
||||
explicit PrimitiveArrayMember(const StringPiece& name)
|
||||
: mName(name.toString()) {}
|
||||
|
||||
void addElement(const T& val) { mElements.push_back(val); }
|
||||
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static final int[] " << mName << "={";
|
||||
|
||||
const auto begin = mElements.begin();
|
||||
const auto end = mElements.end();
|
||||
for (auto current = begin; current != end; ++current) {
|
||||
if (std::distance(begin, current) % kAttribsPerLine == 0) {
|
||||
*out << "\n" << prefix << kIndent << kIndent;
|
||||
}
|
||||
|
||||
*out << *current;
|
||||
if (std::distance(current, end) > 1) {
|
||||
*out << ", ";
|
||||
}
|
||||
}
|
||||
*out << "\n" << prefix << kIndent << "};";
|
||||
}
|
||||
|
||||
void addElement(const T& val) {
|
||||
mElements.push_back(val);
|
||||
}
|
||||
private:
|
||||
std::string mName;
|
||||
std::vector<T> mElements;
|
||||
|
||||
bool empty() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static final int[] " << mName << "={";
|
||||
|
||||
const auto begin = mElements.begin();
|
||||
const auto end = mElements.end();
|
||||
for (auto current = begin; current != end; ++current) {
|
||||
if (std::distance(begin, current) % kAttribsPerLine == 0) {
|
||||
*out << "\n" << prefix << kIndent << kIndent;
|
||||
}
|
||||
|
||||
*out << *current;
|
||||
if (std::distance(current, end) > 1) {
|
||||
*out << ", ";
|
||||
}
|
||||
}
|
||||
*out << "\n" << prefix << kIndent <<"};";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::vector<T> mElements;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
|
||||
};
|
||||
|
||||
using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
|
||||
|
||||
enum class ClassQualifier {
|
||||
None,
|
||||
Static
|
||||
};
|
||||
enum class ClassQualifier { None, Static };
|
||||
|
||||
class ClassDefinition : public ClassMember {
|
||||
public:
|
||||
static bool writeJavaFile(const ClassDefinition* def,
|
||||
const StringPiece& package,
|
||||
bool final,
|
||||
std::ostream* out);
|
||||
public:
|
||||
static bool writeJavaFile(const ClassDefinition* def,
|
||||
const StringPiece& package, bool final,
|
||||
std::ostream* out);
|
||||
|
||||
ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) :
|
||||
mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) {
|
||||
}
|
||||
ClassDefinition(const StringPiece& name, ClassQualifier qualifier,
|
||||
bool createIfEmpty)
|
||||
: mName(name.toString()),
|
||||
mQualifier(qualifier),
|
||||
mCreateIfEmpty(createIfEmpty) {}
|
||||
|
||||
void addMember(std::unique_ptr<ClassMember> member) {
|
||||
mMembers.push_back(std::move(member));
|
||||
}
|
||||
void addMember(std::unique_ptr<ClassMember> member) {
|
||||
mMembers.push_back(std::move(member));
|
||||
}
|
||||
|
||||
bool empty() const override;
|
||||
void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override;
|
||||
bool empty() const override;
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override;
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
ClassQualifier mQualifier;
|
||||
bool mCreateIfEmpty;
|
||||
std::vector<std::unique_ptr<ClassMember>> mMembers;
|
||||
private:
|
||||
std::string mName;
|
||||
ClassQualifier mQualifier;
|
||||
bool mCreateIfEmpty;
|
||||
std::vector<std::unique_ptr<ClassMember>> mMembers;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
|
||||
DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_JAVA_CLASSDEFINITION_H */
|
||||
|
||||
@@ -31,72 +31,74 @@ class AnnotationProcessor;
|
||||
class ClassDefinition;
|
||||
|
||||
struct JavaClassGeneratorOptions {
|
||||
/*
|
||||
* Specifies whether to use the 'final' modifier
|
||||
* on resource entries. Default is true.
|
||||
*/
|
||||
bool useFinal = true;
|
||||
/*
|
||||
* Specifies whether to use the 'final' modifier
|
||||
* on resource entries. Default is true.
|
||||
*/
|
||||
bool useFinal = true;
|
||||
|
||||
enum class SymbolTypes {
|
||||
kAll,
|
||||
kPublicPrivate,
|
||||
kPublic,
|
||||
};
|
||||
enum class SymbolTypes {
|
||||
kAll,
|
||||
kPublicPrivate,
|
||||
kPublic,
|
||||
};
|
||||
|
||||
SymbolTypes types = SymbolTypes::kAll;
|
||||
SymbolTypes types = SymbolTypes::kAll;
|
||||
|
||||
/**
|
||||
* A list of JavaDoc annotations to add to the comments of all generated classes.
|
||||
*/
|
||||
std::vector<std::string> javadocAnnotations;
|
||||
/**
|
||||
* A list of JavaDoc annotations to add to the comments of all generated
|
||||
* classes.
|
||||
*/
|
||||
std::vector<std::string> javadocAnnotations;
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates the R.java file for a resource table.
|
||||
*/
|
||||
class JavaClassGenerator {
|
||||
public:
|
||||
JavaClassGenerator(IAaptContext* context, ResourceTable* table,
|
||||
const JavaClassGeneratorOptions& options);
|
||||
public:
|
||||
JavaClassGenerator(IAaptContext* context, ResourceTable* table,
|
||||
const JavaClassGeneratorOptions& options);
|
||||
|
||||
/*
|
||||
* Writes the R.java file to `out`. Only symbols belonging to `package` are written.
|
||||
* All symbols technically belong to a single package, but linked libraries will
|
||||
* have their names mangled, denoting that they came from a different package.
|
||||
* We need to generate these symbols in a separate file.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool generate(const StringPiece& packageNameToGenerate, std::ostream* out);
|
||||
/*
|
||||
* Writes the R.java file to `out`. Only symbols belonging to `package` are
|
||||
* written.
|
||||
* All symbols technically belong to a single package, but linked libraries
|
||||
* will
|
||||
* have their names mangled, denoting that they came from a different package.
|
||||
* We need to generate these symbols in a separate file.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool generate(const StringPiece& packageNameToGenerate, std::ostream* out);
|
||||
|
||||
bool generate(const StringPiece& packageNameToGenerate,
|
||||
const StringPiece& outputPackageName,
|
||||
std::ostream* out);
|
||||
bool generate(const StringPiece& packageNameToGenerate,
|
||||
const StringPiece& outputPackageName, std::ostream* out);
|
||||
|
||||
const std::string& getError() const;
|
||||
const std::string& getError() const;
|
||||
|
||||
private:
|
||||
bool addMembersToTypeClass(const StringPiece& packageNameToGenerate,
|
||||
const ResourceTablePackage* package,
|
||||
const ResourceTableType* type,
|
||||
ClassDefinition* outTypeClassDef);
|
||||
private:
|
||||
bool addMembersToTypeClass(const StringPiece& packageNameToGenerate,
|
||||
const ResourceTablePackage* package,
|
||||
const ResourceTableType* type,
|
||||
ClassDefinition* outTypeClassDef);
|
||||
|
||||
void addMembersToStyleableClass(const StringPiece& packageNameToGenerate,
|
||||
const std::string& entryName,
|
||||
const Styleable* styleable,
|
||||
ClassDefinition* outStyleableClassDef);
|
||||
void addMembersToStyleableClass(const StringPiece& packageNameToGenerate,
|
||||
const std::string& entryName,
|
||||
const Styleable* styleable,
|
||||
ClassDefinition* outStyleableClassDef);
|
||||
|
||||
bool skipSymbol(SymbolState state);
|
||||
bool skipSymbol(SymbolState state);
|
||||
|
||||
IAaptContext* mContext;
|
||||
ResourceTable* mTable;
|
||||
JavaClassGeneratorOptions mOptions;
|
||||
std::string mError;
|
||||
IAaptContext* mContext;
|
||||
ResourceTable* mTable;
|
||||
JavaClassGeneratorOptions mOptions;
|
||||
std::string mError;
|
||||
};
|
||||
|
||||
inline const std::string& JavaClassGenerator::getError() const {
|
||||
return mError;
|
||||
return mError;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_JAVA_CLASS_GENERATOR_H
|
||||
#endif // AAPT_JAVA_CLASS_GENERATOR_H
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res);
|
||||
std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag,
|
||||
xml::XmlResource* res);
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
|
||||
|
||||
@@ -30,29 +30,31 @@ namespace aapt {
|
||||
namespace proguard {
|
||||
|
||||
class KeepSet {
|
||||
public:
|
||||
inline void addClass(const Source& source, const std::string& className) {
|
||||
mKeepSet[className].insert(source);
|
||||
}
|
||||
public:
|
||||
inline void addClass(const Source& source, const std::string& className) {
|
||||
mKeepSet[className].insert(source);
|
||||
}
|
||||
|
||||
inline void addMethod(const Source& source, const std::string& methodName) {
|
||||
mKeepMethodSet[methodName].insert(source);
|
||||
}
|
||||
inline void addMethod(const Source& source, const std::string& methodName) {
|
||||
mKeepMethodSet[methodName].insert(source);
|
||||
}
|
||||
|
||||
private:
|
||||
friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
|
||||
private:
|
||||
friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
|
||||
|
||||
std::map<std::string, std::set<Source>> mKeepSet;
|
||||
std::map<std::string, std::set<Source>> mKeepMethodSet;
|
||||
std::map<std::string, std::set<Source>> mKeepSet;
|
||||
std::map<std::string, std::set<Source>> mKeepMethodSet;
|
||||
};
|
||||
|
||||
bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet,
|
||||
bool collectProguardRulesForManifest(const Source& source,
|
||||
xml::XmlResource* res, KeepSet* keepSet,
|
||||
bool mainDexOnly = false);
|
||||
bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
|
||||
bool collectProguardRules(const Source& source, xml::XmlResource* res,
|
||||
KeepSet* keepSet);
|
||||
|
||||
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
|
||||
|
||||
} // namespace proguard
|
||||
} // namespace aapt
|
||||
} // namespace proguard
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_PROGUARD_RULES_H
|
||||
#endif // AAPT_PROGUARD_RULES_H
|
||||
|
||||
@@ -25,117 +25,128 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
|
||||
bool shouldGenerateVersionedResource(const ResourceEntry* entry,
|
||||
const ConfigDescription& config,
|
||||
const int sdkVersionToGenerate) {
|
||||
// We assume the caller is trying to generate a version greater than the current configuration.
|
||||
assert(sdkVersionToGenerate > config.sdkVersion);
|
||||
// We assume the caller is trying to generate a version greater than the
|
||||
// current configuration.
|
||||
assert(sdkVersionToGenerate > config.sdkVersion);
|
||||
|
||||
const auto endIter = entry->values.end();
|
||||
auto iter = entry->values.begin();
|
||||
for (; iter != endIter; ++iter) {
|
||||
if ((*iter)->config == config) {
|
||||
break;
|
||||
}
|
||||
const auto endIter = entry->values.end();
|
||||
auto iter = entry->values.begin();
|
||||
for (; iter != endIter; ++iter) {
|
||||
if ((*iter)->config == config) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The source config came from this list, so it should be here.
|
||||
assert(iter != entry->values.end());
|
||||
++iter;
|
||||
// The source config came from this list, so it should be here.
|
||||
assert(iter != entry->values.end());
|
||||
++iter;
|
||||
|
||||
// The next configuration either only varies in sdkVersion, or it is completely different
|
||||
// and therefore incompatible. If it is incompatible, we must generate the versioned resource.
|
||||
// The next configuration either only varies in sdkVersion, or it is
|
||||
// completely different
|
||||
// and therefore incompatible. If it is incompatible, we must generate the
|
||||
// versioned resource.
|
||||
|
||||
// NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
|
||||
// qualifiers, so we need to iterate through the entire list to be sure there
|
||||
// are no higher sdk level versions of this resource.
|
||||
ConfigDescription tempConfig(config);
|
||||
for (; iter != endIter; ++iter) {
|
||||
tempConfig.sdkVersion = (*iter)->config.sdkVersion;
|
||||
if (tempConfig == (*iter)->config) {
|
||||
// The two configs are the same, check the sdk version.
|
||||
return sdkVersionToGenerate < (*iter)->config.sdkVersion;
|
||||
}
|
||||
// NOTE: The ordering of configurations takes sdkVersion as higher precedence
|
||||
// than other
|
||||
// qualifiers, so we need to iterate through the entire list to be sure there
|
||||
// are no higher sdk level versions of this resource.
|
||||
ConfigDescription tempConfig(config);
|
||||
for (; iter != endIter; ++iter) {
|
||||
tempConfig.sdkVersion = (*iter)->config.sdkVersion;
|
||||
if (tempConfig == (*iter)->config) {
|
||||
// The two configs are the same, check the sdk version.
|
||||
return sdkVersionToGenerate < (*iter)->config.sdkVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// No match was found, so we should generate the versioned resource.
|
||||
return true;
|
||||
// No match was found, so we should generate the versioned resource.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
for (auto& type : package->types) {
|
||||
if (type->type != ResourceType::kStyle) {
|
||||
continue;
|
||||
}
|
||||
for (auto& package : table->packages) {
|
||||
for (auto& type : package->types) {
|
||||
if (type->type != ResourceType::kStyle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& entry : type->entries) {
|
||||
for (size_t i = 0; i < entry->values.size(); i++) {
|
||||
ResourceConfigValue* configValue = entry->values[i].get();
|
||||
if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
|
||||
// If this configuration is only used on L-MR1 then we don't need
|
||||
// to do anything since we use private attributes since that version.
|
||||
continue;
|
||||
}
|
||||
for (auto& entry : type->entries) {
|
||||
for (size_t i = 0; i < entry->values.size(); i++) {
|
||||
ResourceConfigValue* configValue = entry->values[i].get();
|
||||
if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
|
||||
// If this configuration is only used on L-MR1 then we don't need
|
||||
// to do anything since we use private attributes since that
|
||||
// version.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Style* style = valueCast<Style>(configValue->value.get())) {
|
||||
Maybe<size_t> minSdkStripped;
|
||||
std::vector<Style::Entry> stripped;
|
||||
if (Style* style = valueCast<Style>(configValue->value.get())) {
|
||||
Maybe<size_t> minSdkStripped;
|
||||
std::vector<Style::Entry> stripped;
|
||||
|
||||
auto iter = style->entries.begin();
|
||||
while (iter != style->entries.end()) {
|
||||
assert(iter->key.id && "IDs must be assigned and linked");
|
||||
auto iter = style->entries.begin();
|
||||
while (iter != style->entries.end()) {
|
||||
assert(iter->key.id && "IDs must be assigned and linked");
|
||||
|
||||
// Find the SDK level that is higher than the configuration allows.
|
||||
const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
|
||||
if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) {
|
||||
// Record that we are about to strip this.
|
||||
stripped.emplace_back(std::move(*iter));
|
||||
// Find the SDK level that is higher than the configuration
|
||||
// allows.
|
||||
const size_t sdkLevel =
|
||||
findAttributeSdkLevel(iter->key.id.value());
|
||||
if (sdkLevel >
|
||||
std::max<size_t>(configValue->config.sdkVersion, 1)) {
|
||||
// Record that we are about to strip this.
|
||||
stripped.emplace_back(std::move(*iter));
|
||||
|
||||
// We use the smallest SDK level to generate the new style.
|
||||
if (minSdkStripped) {
|
||||
minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
|
||||
} else {
|
||||
minSdkStripped = sdkLevel;
|
||||
}
|
||||
|
||||
// Erase this from this style.
|
||||
iter = style->entries.erase(iter);
|
||||
continue;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (minSdkStripped && !stripped.empty()) {
|
||||
// We found attributes from a higher SDK level. Check that
|
||||
// there is no other defined resource for the version we want to
|
||||
// generate.
|
||||
if (shouldGenerateVersionedResource(entry.get(),
|
||||
configValue->config,
|
||||
minSdkStripped.value())) {
|
||||
// Let's create a new Style for this versioned resource.
|
||||
ConfigDescription newConfig(configValue->config);
|
||||
newConfig.sdkVersion = minSdkStripped.value();
|
||||
|
||||
std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
|
||||
newStyle->setComment(style->getComment());
|
||||
newStyle->setSource(style->getSource());
|
||||
|
||||
// Move the previously stripped attributes into this style.
|
||||
newStyle->entries.insert(newStyle->entries.end(),
|
||||
std::make_move_iterator(stripped.begin()),
|
||||
std::make_move_iterator(stripped.end()));
|
||||
|
||||
// Insert the new Resource into the correct place.
|
||||
entry->findOrCreateValue(newConfig, {})->value =
|
||||
std::move(newStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// We use the smallest SDK level to generate the new style.
|
||||
if (minSdkStripped) {
|
||||
minSdkStripped = std::min(minSdkStripped.value(), sdkLevel);
|
||||
} else {
|
||||
minSdkStripped = sdkLevel;
|
||||
}
|
||||
|
||||
// Erase this from this style.
|
||||
iter = style->entries.erase(iter);
|
||||
continue;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (minSdkStripped && !stripped.empty()) {
|
||||
// We found attributes from a higher SDK level. Check that
|
||||
// there is no other defined resource for the version we want to
|
||||
// generate.
|
||||
if (shouldGenerateVersionedResource(entry.get(),
|
||||
configValue->config,
|
||||
minSdkStripped.value())) {
|
||||
// Let's create a new Style for this versioned resource.
|
||||
ConfigDescription newConfig(configValue->config);
|
||||
newConfig.sdkVersion = minSdkStripped.value();
|
||||
|
||||
std::unique_ptr<Style> newStyle(
|
||||
style->clone(&table->stringPool));
|
||||
newStyle->setComment(style->getComment());
|
||||
newStyle->setSource(style->getSource());
|
||||
|
||||
// Move the previously stripped attributes into this style.
|
||||
newStyle->entries.insert(
|
||||
newStyle->entries.end(),
|
||||
std::make_move_iterator(stripped.begin()),
|
||||
std::make_move_iterator(stripped.end()));
|
||||
|
||||
// Insert the new Resource into the correct place.
|
||||
entry->findOrCreateValue(newConfig, {})->value =
|
||||
std::move(newStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -21,102 +21,114 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(AutoVersionerTest, GenerateVersionedResources) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig =
|
||||
test::parseConfigOrDie("sw600dp-land");
|
||||
|
||||
ResourceEntry entry("foo");
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
ResourceEntry entry("foo");
|
||||
entry.values.push_back(
|
||||
util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(
|
||||
util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
entry.values.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
|
||||
}
|
||||
|
||||
TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13");
|
||||
const ConfigDescription v21Config = test::parseConfigOrDie("v21");
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription sw600dpV13Config =
|
||||
test::parseConfigOrDie("sw600dp-v13");
|
||||
const ConfigDescription v21Config = test::parseConfigOrDie("v21");
|
||||
|
||||
ResourceEntry entry("foo");
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
|
||||
ResourceEntry entry("foo");
|
||||
entry.values.push_back(
|
||||
util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
|
||||
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
|
||||
}
|
||||
|
||||
TEST(AutoVersionerTest, VersionStylesForTable) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.setPackageId("app", 0x7f)
|
||||
.addValue("app:style/Foo", test::parseConfigOrDie("v4"), ResourceId(0x7f020000),
|
||||
test::StyleBuilder()
|
||||
.addItem("android:attr/onClick", ResourceId(0x0101026f),
|
||||
util::make_unique<Id>())
|
||||
.addItem("android:attr/paddingStart", ResourceId(0x010103b3),
|
||||
util::make_unique<Id>())
|
||||
.addItem("android:attr/requiresSmallestWidthDp",
|
||||
ResourceId(0x01010364), util::make_unique<Id>())
|
||||
.addItem("android:attr/colorAccent", ResourceId(0x01010435),
|
||||
util::make_unique<Id>())
|
||||
.build())
|
||||
.addValue("app:style/Foo", test::parseConfigOrDie("v21"), ResourceId(0x7f020000),
|
||||
test::StyleBuilder()
|
||||
.addItem("android:attr/paddingEnd", ResourceId(0x010103b4),
|
||||
util::make_unique<Id>())
|
||||
.build())
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.setPackageId("app", 0x7f)
|
||||
.addValue(
|
||||
"app:style/Foo", test::parseConfigOrDie("v4"),
|
||||
ResourceId(0x7f020000),
|
||||
test::StyleBuilder()
|
||||
.addItem("android:attr/onClick", ResourceId(0x0101026f),
|
||||
util::make_unique<Id>())
|
||||
.addItem("android:attr/paddingStart", ResourceId(0x010103b3),
|
||||
util::make_unique<Id>())
|
||||
.addItem("android:attr/requiresSmallestWidthDp",
|
||||
ResourceId(0x01010364), util::make_unique<Id>())
|
||||
.addItem("android:attr/colorAccent", ResourceId(0x01010435),
|
||||
util::make_unique<Id>())
|
||||
.build())
|
||||
.addValue(
|
||||
"app:style/Foo", test::parseConfigOrDie("v21"),
|
||||
ResourceId(0x7f020000),
|
||||
test::StyleBuilder()
|
||||
.addItem("android:attr/paddingEnd", ResourceId(0x010103b4),
|
||||
util::make_unique<Id>())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
|
||||
.setCompilationPackage("app")
|
||||
.setPackageId(0x7f)
|
||||
.build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
|
||||
.setCompilationPackage("app")
|
||||
.setPackageId(0x7f)
|
||||
.build();
|
||||
|
||||
AutoVersioner versioner;
|
||||
ASSERT_TRUE(versioner.consume(context.get(), table.get()));
|
||||
AutoVersioner versioner;
|
||||
ASSERT_TRUE(versioner.consume(context.get(), table.get()));
|
||||
|
||||
Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v4"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 1u);
|
||||
AAPT_ASSERT_TRUE(style->entries.front().key.name);
|
||||
EXPECT_EQ(style->entries.front().key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v4"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 1u);
|
||||
AAPT_ASSERT_TRUE(style->entries.front().key.name);
|
||||
EXPECT_EQ(style->entries.front().key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v13"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 2u);
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(style->entries[0].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(style->entries[1].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v13"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 2u);
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(style->entries[0].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(style->entries[1].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
|
||||
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v17"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 3u);
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(style->entries[0].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(style->entries[1].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
|
||||
AAPT_ASSERT_TRUE(style->entries[2].key.name);
|
||||
EXPECT_EQ(style->entries[2].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/paddingStart"));
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v17"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 3u);
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(style->entries[0].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/onClick"));
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(style->entries[1].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
|
||||
AAPT_ASSERT_TRUE(style->entries[2].key.name);
|
||||
EXPECT_EQ(style->entries[2].key.name.value(),
|
||||
test::parseNameOrDie("android:attr/paddingStart"));
|
||||
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v21"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 1u);
|
||||
AAPT_ASSERT_TRUE(style->entries.front().key.name);
|
||||
EXPECT_EQ(style->entries.front().key.name.value(),
|
||||
test::parseNameOrDie("android:attr/paddingEnd"));
|
||||
style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
|
||||
test::parseConfigOrDie("v21"));
|
||||
ASSERT_NE(style, nullptr);
|
||||
ASSERT_EQ(style->entries.size(), 1u);
|
||||
AAPT_ASSERT_TRUE(style->entries.front().key.name);
|
||||
EXPECT_EQ(style->entries.front().key.name.value(),
|
||||
test::parseNameOrDie("android:attr/paddingEnd"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,106 +30,121 @@ class ResourceEntry;
|
||||
struct ConfigDescription;
|
||||
|
||||
/**
|
||||
* Defines the location in which a value exists. This determines visibility of other
|
||||
* Defines the location in which a value exists. This determines visibility of
|
||||
* other
|
||||
* package's private symbols.
|
||||
*/
|
||||
struct CallSite {
|
||||
ResourceNameRef resource;
|
||||
ResourceNameRef resource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a versioned resource should be created. If a versioned resource already
|
||||
* Determines whether a versioned resource should be created. If a versioned
|
||||
* resource already
|
||||
* exists, it takes precedence.
|
||||
*/
|
||||
bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
|
||||
bool shouldGenerateVersionedResource(const ResourceEntry* entry,
|
||||
const ConfigDescription& config,
|
||||
const int sdkVersionToGenerate);
|
||||
|
||||
class AutoVersioner : public IResourceTableConsumer {
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
class XmlAutoVersioner : public IXmlResourceConsumer {
|
||||
public:
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
public:
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
};
|
||||
|
||||
class VersionCollapser : public IResourceTableConsumer {
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes duplicated key-value entries from dominated resources.
|
||||
*/
|
||||
class ResourceDeduper : public IResourceTableConsumer {
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
public:
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* If any attribute resource values are defined as public, this consumer will move all private
|
||||
* attribute resource values to a private ^private-attr type, avoiding backwards compatibility
|
||||
* If any attribute resource values are defined as public, this consumer will
|
||||
* move all private
|
||||
* attribute resource values to a private ^private-attr type, avoiding backwards
|
||||
* compatibility
|
||||
* issues with new apps running on old platforms.
|
||||
*
|
||||
* The Android platform ignores resource attributes it doesn't recognize, so an app developer can
|
||||
* use new attributes in their layout XML files without worrying about versioning. This assumption
|
||||
* actually breaks on older platforms. OEMs may add private attributes that are used internally.
|
||||
* AAPT originally assigned all private attributes IDs immediately proceeding the public attributes'
|
||||
* The Android platform ignores resource attributes it doesn't recognize, so an
|
||||
* app developer can
|
||||
* use new attributes in their layout XML files without worrying about
|
||||
* versioning. This assumption
|
||||
* actually breaks on older platforms. OEMs may add private attributes that are
|
||||
* used internally.
|
||||
* AAPT originally assigned all private attributes IDs immediately proceeding
|
||||
* the public attributes'
|
||||
* IDs.
|
||||
*
|
||||
* This means that on a newer Android platform, an ID previously assigned to a private attribute
|
||||
* This means that on a newer Android platform, an ID previously assigned to a
|
||||
* private attribute
|
||||
* may end up assigned to a public attribute.
|
||||
*
|
||||
* App developers assume using the newer attribute is safe on older platforms because it will
|
||||
* be ignored. Instead, the platform thinks the new attribute is an older, private attribute and
|
||||
* will interpret it as such. This leads to unintended styling and exceptions thrown due to
|
||||
* App developers assume using the newer attribute is safe on older platforms
|
||||
* because it will
|
||||
* be ignored. Instead, the platform thinks the new attribute is an older,
|
||||
* private attribute and
|
||||
* will interpret it as such. This leads to unintended styling and exceptions
|
||||
* thrown due to
|
||||
* unexpected types.
|
||||
*
|
||||
* By moving the private attributes to a completely different type, this ID conflict will never
|
||||
* By moving the private attributes to a completely different type, this ID
|
||||
* conflict will never
|
||||
* occur.
|
||||
*/
|
||||
struct PrivateAttributeMover : public IResourceTableConsumer {
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes namespace nodes and URI information from the XmlResource.
|
||||
*
|
||||
* Once an XmlResource is processed by this consumer, it is no longer able to have its attributes
|
||||
* parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker.
|
||||
* Once an XmlResource is processed by this consumer, it is no longer able to
|
||||
* have its attributes
|
||||
* parsed. As such, this XmlResource must have already been processed by
|
||||
* XmlReferenceLinker.
|
||||
*/
|
||||
class XmlNamespaceRemover : public IXmlResourceConsumer {
|
||||
private:
|
||||
bool mKeepUris;
|
||||
private:
|
||||
bool mKeepUris;
|
||||
|
||||
public:
|
||||
XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris) {
|
||||
};
|
||||
public:
|
||||
XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris){};
|
||||
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves attributes in the XmlResource and compiles string values to resource values.
|
||||
* Resolves attributes in the XmlResource and compiles string values to resource
|
||||
* values.
|
||||
* Once an XmlResource is processed by this linker, it is ready to be flattened.
|
||||
*/
|
||||
class XmlReferenceLinker : public IXmlResourceConsumer {
|
||||
private:
|
||||
std::set<int> mSdkLevelsFound;
|
||||
private:
|
||||
std::set<int> mSdkLevelsFound;
|
||||
|
||||
public:
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
public:
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
|
||||
/**
|
||||
* Once the XmlResource has been consumed, this returns the various SDK levels in which
|
||||
* framework attributes used within the XML document were defined.
|
||||
*/
|
||||
inline const std::set<int>& getSdkLevels() const {
|
||||
return mSdkLevelsFound;
|
||||
}
|
||||
/**
|
||||
* Once the XmlResource has been consumed, this returns the various SDK levels
|
||||
* in which
|
||||
* framework attributes used within the XML document were defined.
|
||||
*/
|
||||
inline const std::set<int>& getSdkLevels() const { return mSdkLevelsFound; }
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_LINKER_LINKERS_H */
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceUtils.h"
|
||||
#include "link/ManifestFixer.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "util/Util.h"
|
||||
#include "xml/XmlActionExecutor.h"
|
||||
#include "xml/XmlDom.h"
|
||||
@@ -25,295 +25,310 @@
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* This is how PackageManager builds class names from AndroidManifest.xml entries.
|
||||
* This is how PackageManager builds class names from AndroidManifest.xml
|
||||
* entries.
|
||||
*/
|
||||
static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
|
||||
SourcePathDiagnostics* diag) {
|
||||
// We allow unqualified class names (ie: .HelloActivity)
|
||||
// Since we don't know the package name, we can just make a fake one here and
|
||||
// the test will be identical as long as the real package name is valid too.
|
||||
Maybe<std::string> fullyQualifiedClassName =
|
||||
util::getFullyQualifiedClassName("a", attr->value);
|
||||
// We allow unqualified class names (ie: .HelloActivity)
|
||||
// Since we don't know the package name, we can just make a fake one here and
|
||||
// the test will be identical as long as the real package name is valid too.
|
||||
Maybe<std::string> fullyQualifiedClassName =
|
||||
util::getFullyQualifiedClassName("a", attr->value);
|
||||
|
||||
StringPiece qualifiedClassName = fullyQualifiedClassName
|
||||
? fullyQualifiedClassName.value() : attr->value;
|
||||
StringPiece qualifiedClassName =
|
||||
fullyQualifiedClassName ? fullyQualifiedClassName.value() : attr->value;
|
||||
|
||||
if (!util::isJavaClassName(qualifiedClassName)) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "attribute 'android:name' in <"
|
||||
<< el->name << "> tag must be a valid Java class name");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
|
||||
return nameIsJavaClassName(el, attr, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
|
||||
return nameIsJavaClassName(el, attr, diag);
|
||||
}
|
||||
if (!util::isJavaClassName(qualifiedClassName)) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "<" << el->name << "> is missing attribute 'android:name'");
|
||||
<< "attribute 'android:name' in <" << el->name
|
||||
<< "> tag must be a valid Java class name");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool optionalNameIsJavaClassName(xml::Element* el,
|
||||
SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
|
||||
return nameIsJavaClassName(el, attr, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool requiredNameIsJavaClassName(xml::Element* el,
|
||||
SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) {
|
||||
return nameIsJavaClassName(el, attr, diag);
|
||||
}
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "<" << el->name << "> is missing attribute 'android:name'");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
xml::Attribute* attr = el->findAttribute({}, "package");
|
||||
if (!attr) {
|
||||
diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute");
|
||||
return false;
|
||||
} else if (ResourceUtils::isReference(attr->value)) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "attribute 'package' in <manifest> tag must not be a reference");
|
||||
return false;
|
||||
} else if (!util::isJavaPackageName(attr->value)) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "attribute 'package' in <manifest> tag is not a valid Java package name: '"
|
||||
<< attr->value << "'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
xml::Attribute* attr = el->findAttribute({}, "package");
|
||||
if (!attr) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "<manifest> tag is missing 'package' attribute");
|
||||
return false;
|
||||
} else if (ResourceUtils::isReference(attr->value)) {
|
||||
diag->error(
|
||||
DiagMessage(el->lineNumber)
|
||||
<< "attribute 'package' in <manifest> tag must not be a reference");
|
||||
return false;
|
||||
} else if (!util::isJavaPackageName(attr->value)) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "attribute 'package' in <manifest> tag is not a valid Java "
|
||||
"package name: '"
|
||||
<< attr->value << "'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The coreApp attribute in <manifest> is not a regular AAPT attribute, so type checking on it
|
||||
* The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
|
||||
* checking on it
|
||||
* is manual.
|
||||
*/
|
||||
static bool fixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->findAttribute("", "coreApp")) {
|
||||
std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseBool(attr->value);
|
||||
if (!result) {
|
||||
diag->error(DiagMessage(el->lineNumber) << "attribute coreApp must be a boolean");
|
||||
return false;
|
||||
}
|
||||
attr->compiledValue = std::move(result);
|
||||
if (xml::Attribute* attr = el->findAttribute("", "coreApp")) {
|
||||
std::unique_ptr<BinaryPrimitive> result =
|
||||
ResourceUtils::tryParseBool(attr->value);
|
||||
if (!result) {
|
||||
diag->error(DiagMessage(el->lineNumber)
|
||||
<< "attribute coreApp must be a boolean");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
attr->compiledValue = std::move(result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) {
|
||||
// First verify some options.
|
||||
if (mOptions.renameManifestPackage) {
|
||||
if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
|
||||
diag->error(DiagMessage() << "invalid manifest package override '"
|
||||
<< mOptions.renameManifestPackage.value() << "'");
|
||||
return false;
|
||||
}
|
||||
bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor,
|
||||
IDiagnostics* diag) {
|
||||
// First verify some options.
|
||||
if (mOptions.renameManifestPackage) {
|
||||
if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
|
||||
diag->error(DiagMessage() << "invalid manifest package override '"
|
||||
<< mOptions.renameManifestPackage.value()
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mOptions.renameInstrumentationTargetPackage) {
|
||||
if (!util::isJavaPackageName(
|
||||
mOptions.renameInstrumentationTargetPackage.value())) {
|
||||
diag->error(DiagMessage()
|
||||
<< "invalid instrumentation target package override '"
|
||||
<< mOptions.renameInstrumentationTargetPackage.value()
|
||||
<< "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Common intent-filter actions.
|
||||
xml::XmlNodeAction intentFilterAction;
|
||||
intentFilterAction["action"];
|
||||
intentFilterAction["category"];
|
||||
intentFilterAction["data"];
|
||||
|
||||
// Common meta-data actions.
|
||||
xml::XmlNodeAction metaDataAction;
|
||||
|
||||
// Manifest actions.
|
||||
xml::XmlNodeAction& manifestAction = (*executor)["manifest"];
|
||||
manifestAction.action(verifyManifest);
|
||||
manifestAction.action(fixCoreAppAttribute);
|
||||
manifestAction.action([&](xml::Element* el) -> bool {
|
||||
if (mOptions.versionNameDefault) {
|
||||
if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
|
||||
el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "versionName",
|
||||
mOptions.versionNameDefault.value()});
|
||||
}
|
||||
}
|
||||
|
||||
if (mOptions.renameInstrumentationTargetPackage) {
|
||||
if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
|
||||
diag->error(DiagMessage() << "invalid instrumentation target package override '"
|
||||
<< mOptions.renameInstrumentationTargetPackage.value() << "'");
|
||||
return false;
|
||||
}
|
||||
if (mOptions.versionCodeDefault) {
|
||||
if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
|
||||
el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "versionCode",
|
||||
mOptions.versionCodeDefault.value()});
|
||||
}
|
||||
}
|
||||
|
||||
// Common intent-filter actions.
|
||||
xml::XmlNodeAction intentFilterAction;
|
||||
intentFilterAction["action"];
|
||||
intentFilterAction["category"];
|
||||
intentFilterAction["data"];
|
||||
|
||||
// Common meta-data actions.
|
||||
xml::XmlNodeAction metaDataAction;
|
||||
|
||||
// Manifest actions.
|
||||
xml::XmlNodeAction& manifestAction = (*executor)["manifest"];
|
||||
manifestAction.action(verifyManifest);
|
||||
manifestAction.action(fixCoreAppAttribute);
|
||||
manifestAction.action([&](xml::Element* el) -> bool {
|
||||
if (mOptions.versionNameDefault) {
|
||||
if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
|
||||
el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid,
|
||||
"versionName",
|
||||
mOptions.versionNameDefault.value() });
|
||||
}
|
||||
}
|
||||
|
||||
if (mOptions.versionCodeDefault) {
|
||||
if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
|
||||
el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid,
|
||||
"versionCode",
|
||||
mOptions.versionCodeDefault.value() });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Meta tags.
|
||||
manifestAction["eat-comment"];
|
||||
|
||||
// Uses-sdk actions.
|
||||
manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool {
|
||||
if (mOptions.minSdkVersionDefault &&
|
||||
el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
|
||||
// There was no minSdkVersion defined and we have a default to assign.
|
||||
el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid, "minSdkVersion",
|
||||
mOptions.minSdkVersionDefault.value() });
|
||||
}
|
||||
|
||||
if (mOptions.targetSdkVersionDefault &&
|
||||
el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) {
|
||||
// There was no targetSdkVersion defined and we have a default to assign.
|
||||
el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid, "targetSdkVersion",
|
||||
mOptions.targetSdkVersionDefault.value() });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Instrumentation actions.
|
||||
manifestAction["instrumentation"].action([&](xml::Element* el) -> bool {
|
||||
if (!mOptions.renameInstrumentationTargetPackage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "targetPackage")) {
|
||||
attr->value = mOptions.renameInstrumentationTargetPackage.value();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
manifestAction["original-package"];
|
||||
manifestAction["protected-broadcast"];
|
||||
manifestAction["uses-permission"];
|
||||
manifestAction["permission"];
|
||||
manifestAction["permission-tree"];
|
||||
manifestAction["permission-group"];
|
||||
|
||||
manifestAction["uses-configuration"];
|
||||
manifestAction["uses-feature"];
|
||||
manifestAction["supports-screens"];
|
||||
|
||||
manifestAction["compatible-screens"];
|
||||
manifestAction["compatible-screens"]["screen"];
|
||||
|
||||
manifestAction["supports-gl-texture"];
|
||||
|
||||
// Application actions.
|
||||
xml::XmlNodeAction& applicationAction = manifestAction["application"];
|
||||
applicationAction.action(optionalNameIsJavaClassName);
|
||||
|
||||
// Uses library actions.
|
||||
applicationAction["uses-library"];
|
||||
|
||||
// Meta-data.
|
||||
applicationAction["meta-data"] = metaDataAction;
|
||||
|
||||
// Activity actions.
|
||||
applicationAction["activity"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["activity"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["activity"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Activity alias actions.
|
||||
applicationAction["activity-alias"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["activity-alias"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Service actions.
|
||||
applicationAction["service"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["service"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["service"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Receiver actions.
|
||||
applicationAction["receiver"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["receiver"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["receiver"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Provider actions.
|
||||
applicationAction["provider"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["provider"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["provider"]["meta-data"] = metaDataAction;
|
||||
applicationAction["provider"]["grant-uri-permissions"];
|
||||
applicationAction["provider"]["path-permissions"];
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Meta tags.
|
||||
manifestAction["eat-comment"];
|
||||
|
||||
// Uses-sdk actions.
|
||||
manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool {
|
||||
if (mOptions.minSdkVersionDefault &&
|
||||
el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
|
||||
// There was no minSdkVersion defined and we have a default to assign.
|
||||
el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "minSdkVersion",
|
||||
mOptions.minSdkVersionDefault.value()});
|
||||
}
|
||||
|
||||
if (mOptions.targetSdkVersionDefault &&
|
||||
el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) {
|
||||
// There was no targetSdkVersion defined and we have a default to assign.
|
||||
el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "targetSdkVersion",
|
||||
mOptions.targetSdkVersionDefault.value()});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Instrumentation actions.
|
||||
manifestAction["instrumentation"].action([&](xml::Element* el) -> bool {
|
||||
if (!mOptions.renameInstrumentationTargetPackage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (xml::Attribute* attr =
|
||||
el->findAttribute(xml::kSchemaAndroid, "targetPackage")) {
|
||||
attr->value = mOptions.renameInstrumentationTargetPackage.value();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
manifestAction["original-package"];
|
||||
manifestAction["protected-broadcast"];
|
||||
manifestAction["uses-permission"];
|
||||
manifestAction["permission"];
|
||||
manifestAction["permission-tree"];
|
||||
manifestAction["permission-group"];
|
||||
|
||||
manifestAction["uses-configuration"];
|
||||
manifestAction["uses-feature"];
|
||||
manifestAction["supports-screens"];
|
||||
|
||||
manifestAction["compatible-screens"];
|
||||
manifestAction["compatible-screens"]["screen"];
|
||||
|
||||
manifestAction["supports-gl-texture"];
|
||||
|
||||
// Application actions.
|
||||
xml::XmlNodeAction& applicationAction = manifestAction["application"];
|
||||
applicationAction.action(optionalNameIsJavaClassName);
|
||||
|
||||
// Uses library actions.
|
||||
applicationAction["uses-library"];
|
||||
|
||||
// Meta-data.
|
||||
applicationAction["meta-data"] = metaDataAction;
|
||||
|
||||
// Activity actions.
|
||||
applicationAction["activity"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["activity"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["activity"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Activity alias actions.
|
||||
applicationAction["activity-alias"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["activity-alias"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Service actions.
|
||||
applicationAction["service"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["service"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["service"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Receiver actions.
|
||||
applicationAction["receiver"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["receiver"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["receiver"]["meta-data"] = metaDataAction;
|
||||
|
||||
// Provider actions.
|
||||
applicationAction["provider"].action(requiredNameIsJavaClassName);
|
||||
applicationAction["provider"]["intent-filter"] = intentFilterAction;
|
||||
applicationAction["provider"]["meta-data"] = metaDataAction;
|
||||
applicationAction["provider"]["grant-uri-permissions"];
|
||||
applicationAction["provider"]["path-permissions"];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class FullyQualifiedClassNameVisitor : public xml::Visitor {
|
||||
public:
|
||||
using xml::Visitor::visit;
|
||||
public:
|
||||
using xml::Visitor::visit;
|
||||
|
||||
explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : mPackage(package) {
|
||||
}
|
||||
explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
|
||||
: mPackage(package) {}
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
for (xml::Attribute& attr : el->attributes) {
|
||||
if (attr.namespaceUri == xml::kSchemaAndroid
|
||||
&& mClassAttributes.find(attr.name) != mClassAttributes.end()) {
|
||||
if (Maybe<std::string> newValue =
|
||||
util::getFullyQualifiedClassName(mPackage, attr.value)) {
|
||||
attr.value = std::move(newValue.value());
|
||||
}
|
||||
}
|
||||
void visit(xml::Element* el) override {
|
||||
for (xml::Attribute& attr : el->attributes) {
|
||||
if (attr.namespaceUri == xml::kSchemaAndroid &&
|
||||
mClassAttributes.find(attr.name) != mClassAttributes.end()) {
|
||||
if (Maybe<std::string> newValue =
|
||||
util::getFullyQualifiedClassName(mPackage, attr.value)) {
|
||||
attr.value = std::move(newValue.value());
|
||||
}
|
||||
|
||||
// Super implementation to iterate over the children.
|
||||
xml::Visitor::visit(el);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
StringPiece mPackage;
|
||||
std::unordered_set<StringPiece> mClassAttributes = { "name" };
|
||||
// Super implementation to iterate over the children.
|
||||
xml::Visitor::visit(el);
|
||||
}
|
||||
|
||||
private:
|
||||
StringPiece mPackage;
|
||||
std::unordered_set<StringPiece> mClassAttributes = {"name"};
|
||||
};
|
||||
|
||||
static bool renameManifestPackage(const StringPiece& packageOverride, xml::Element* manifestEl) {
|
||||
xml::Attribute* attr = manifestEl->findAttribute({}, "package");
|
||||
static bool renameManifestPackage(const StringPiece& packageOverride,
|
||||
xml::Element* manifestEl) {
|
||||
xml::Attribute* attr = manifestEl->findAttribute({}, "package");
|
||||
|
||||
// We've already verified that the manifest element is present, with a package name specified.
|
||||
assert(attr);
|
||||
// We've already verified that the manifest element is present, with a package
|
||||
// name specified.
|
||||
assert(attr);
|
||||
|
||||
std::string originalPackage = std::move(attr->value);
|
||||
attr->value = packageOverride.toString();
|
||||
std::string originalPackage = std::move(attr->value);
|
||||
attr->value = packageOverride.toString();
|
||||
|
||||
FullyQualifiedClassNameVisitor visitor(originalPackage);
|
||||
manifestEl->accept(&visitor);
|
||||
return true;
|
||||
FullyQualifiedClassNameVisitor visitor(originalPackage);
|
||||
manifestEl->accept(&visitor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
|
||||
xml::Element* root = xml::findRootElement(doc->root.get());
|
||||
if (!root || !root->namespaceUri.empty() || root->name != "manifest") {
|
||||
context->getDiagnostics()->error(DiagMessage(doc->file.source)
|
||||
<< "root tag must be <manifest>");
|
||||
return false;
|
||||
}
|
||||
xml::Element* root = xml::findRootElement(doc->root.get());
|
||||
if (!root || !root->namespaceUri.empty() || root->name != "manifest") {
|
||||
context->getDiagnostics()->error(DiagMessage(doc->file.source)
|
||||
<< "root tag must be <manifest>");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)
|
||||
&& root->findChild({}, "uses-sdk") == nullptr) {
|
||||
// Auto insert a <uses-sdk> element.
|
||||
std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
|
||||
usesSdk->name = "uses-sdk";
|
||||
root->addChild(std::move(usesSdk));
|
||||
}
|
||||
if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) &&
|
||||
root->findChild({}, "uses-sdk") == nullptr) {
|
||||
// Auto insert a <uses-sdk> element.
|
||||
std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
|
||||
usesSdk->name = "uses-sdk";
|
||||
root->addChild(std::move(usesSdk));
|
||||
}
|
||||
|
||||
xml::XmlActionExecutor executor;
|
||||
if (!buildRules(&executor, context->getDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
xml::XmlActionExecutor executor;
|
||||
if (!buildRules(&executor, context->getDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
|
||||
doc)) {
|
||||
return false;
|
||||
}
|
||||
if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist,
|
||||
context->getDiagnostics(), doc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mOptions.renameManifestPackage) {
|
||||
// Rename manifest package outside of the XmlActionExecutor.
|
||||
// We need to extract the old package name and FullyQualify all class names.
|
||||
if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
|
||||
return false;
|
||||
}
|
||||
if (mOptions.renameManifestPackage) {
|
||||
// Rename manifest package outside of the XmlActionExecutor.
|
||||
// We need to extract the old package name and FullyQualify all class names.
|
||||
if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
namespace aapt {
|
||||
|
||||
struct ManifestFixerOptions {
|
||||
Maybe<std::string> minSdkVersionDefault;
|
||||
Maybe<std::string> targetSdkVersionDefault;
|
||||
Maybe<std::string> renameManifestPackage;
|
||||
Maybe<std::string> renameInstrumentationTargetPackage;
|
||||
Maybe<std::string> versionNameDefault;
|
||||
Maybe<std::string> versionCodeDefault;
|
||||
Maybe<std::string> minSdkVersionDefault;
|
||||
Maybe<std::string> targetSdkVersionDefault;
|
||||
Maybe<std::string> renameManifestPackage;
|
||||
Maybe<std::string> renameInstrumentationTargetPackage;
|
||||
Maybe<std::string> versionNameDefault;
|
||||
Maybe<std::string> versionCodeDefault;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,18 +40,18 @@ struct ManifestFixerOptions {
|
||||
* where specified with ManifestFixerOptions.
|
||||
*/
|
||||
class ManifestFixer : public IXmlResourceConsumer {
|
||||
public:
|
||||
explicit ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
|
||||
}
|
||||
public:
|
||||
explicit ManifestFixer(const ManifestFixerOptions& options)
|
||||
: mOptions(options) {}
|
||||
|
||||
bool consume(IAaptContext* context, xml::XmlResource* doc) override;
|
||||
bool consume(IAaptContext* context, xml::XmlResource* doc) override;
|
||||
|
||||
private:
|
||||
bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
|
||||
private:
|
||||
bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
|
||||
|
||||
ManifestFixerOptions mOptions;
|
||||
ManifestFixerOptions mOptions;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_LINK_MANIFESTFIXER_H */
|
||||
|
||||
@@ -23,254 +23,274 @@
|
||||
namespace aapt {
|
||||
|
||||
struct ManifestFixerTest : public ::testing::Test {
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder()
|
||||
.setCompilationPackage("android")
|
||||
.setPackageId(0x01)
|
||||
.setNameManglerPolicy(NameManglerPolicy{ "android" })
|
||||
.addSymbolSource(test::StaticSymbolSourceBuilder()
|
||||
.addSymbol("android:attr/package", ResourceId(0x01010000),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING)
|
||||
.build())
|
||||
.addSymbol("android:attr/minSdkVersion", ResourceId(0x01010001),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING |
|
||||
android::ResTable_map::TYPE_INTEGER)
|
||||
.build())
|
||||
.addSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING |
|
||||
android::ResTable_map::TYPE_INTEGER)
|
||||
.build())
|
||||
.addSymbol("android:string/str", ResourceId(0x01060000))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
void SetUp() override {
|
||||
mContext =
|
||||
test::ContextBuilder()
|
||||
.setCompilationPackage("android")
|
||||
.setPackageId(0x01)
|
||||
.setNameManglerPolicy(NameManglerPolicy{"android"})
|
||||
.addSymbolSource(
|
||||
test::StaticSymbolSourceBuilder()
|
||||
.addSymbol(
|
||||
"android:attr/package", ResourceId(0x01010000),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING)
|
||||
.build())
|
||||
.addSymbol(
|
||||
"android:attr/minSdkVersion", ResourceId(0x01010001),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING |
|
||||
android::ResTable_map::TYPE_INTEGER)
|
||||
.build())
|
||||
.addSymbol(
|
||||
"android:attr/targetSdkVersion", ResourceId(0x01010002),
|
||||
test::AttributeBuilder()
|
||||
.setTypeMask(android::ResTable_map::TYPE_STRING |
|
||||
android::ResTable_map::TYPE_INTEGER)
|
||||
.build())
|
||||
.addSymbol("android:string/str", ResourceId(0x01060000))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
|
||||
return verifyWithOptions(str, {});
|
||||
}
|
||||
std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
|
||||
return verifyWithOptions(str, {});
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
|
||||
const ManifestFixerOptions& options) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
|
||||
ManifestFixer fixer(options);
|
||||
if (fixer.consume(mContext.get(), doc.get())) {
|
||||
return doc;
|
||||
}
|
||||
return {};
|
||||
std::unique_ptr<xml::XmlResource> verifyWithOptions(
|
||||
const StringPiece& str, const ManifestFixerOptions& options) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
|
||||
ManifestFixer fixer(options);
|
||||
if (fixer.consume(mContext.get(), doc.get())) {
|
||||
return doc;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
|
||||
EXPECT_EQ(nullptr, verify("<other-tag />"));
|
||||
EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
|
||||
EXPECT_EQ(nullptr, verify("<other-tag />"));
|
||||
EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
|
||||
EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
|
||||
EXPECT_EQ(nullptr,
|
||||
verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
|
||||
"android:package=\"com.android\" />"));
|
||||
EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
|
||||
EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
|
||||
EXPECT_EQ(nullptr,
|
||||
verify("<manifest package=\"com.android.google.Class$1\" />"));
|
||||
EXPECT_EQ(nullptr, verify("<manifest "
|
||||
"xmlns:android=\"http://schemas.android.com/apk/"
|
||||
"res/android\" "
|
||||
"android:package=\"com.android\" />"));
|
||||
EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
|
||||
ManifestFixerOptions options = { std::string("8"), std::string("22") };
|
||||
ManifestFixerOptions options = {std::string("8"), std::string("22")};
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
|
||||
</manifest>)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
</manifest>)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
xml::Element* el;
|
||||
xml::Attribute* attr;
|
||||
xml::Element* el;
|
||||
xml::Attribute* attr;
|
||||
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("7", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("21", attr->value);
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("7", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("21", attr->value);
|
||||
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<uses-sdk android:targetSdkVersion="21" />
|
||||
</manifest>)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
</manifest>)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("21", attr->value);
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("21", attr->value);
|
||||
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<uses-sdk />
|
||||
</manifest>)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
</manifest>)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("22", attr->value);
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("22", attr->value);
|
||||
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android" />)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
package="android" />)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("22", attr->value);
|
||||
el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = el->findChild({}, "uses-sdk");
|
||||
ASSERT_NE(nullptr, el);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("8", attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ("22", attr->value);
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
|
||||
ManifestFixerOptions options;
|
||||
options.renameManifestPackage = std::string("com.android");
|
||||
ManifestFixerOptions options;
|
||||
options.renameManifestPackage = std::string("com.android");
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application android:name=".MainApplication" text="hello">
|
||||
<activity android:name=".activity.Start" />
|
||||
<receiver android:name="com.google.android.Receiver" />
|
||||
</application>
|
||||
</manifest>)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
</manifest>)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
|
||||
xml::Attribute* attr = nullptr;
|
||||
xml::Attribute* attr = nullptr;
|
||||
|
||||
attr = manifestEl->findAttribute({},"package");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("com.android"), attr->value);
|
||||
attr = manifestEl->findAttribute({}, "package");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("com.android"), attr->value);
|
||||
|
||||
xml::Element* applicationEl = manifestEl->findChild({}, "application");
|
||||
ASSERT_NE(nullptr, applicationEl);
|
||||
xml::Element* applicationEl = manifestEl->findChild({}, "application");
|
||||
ASSERT_NE(nullptr, applicationEl);
|
||||
|
||||
attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("android.MainApplication"), attr->value);
|
||||
attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("android.MainApplication"), attr->value);
|
||||
|
||||
attr = applicationEl->findAttribute({}, "text");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("hello"), attr->value);
|
||||
attr = applicationEl->findAttribute({}, "text");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("hello"), attr->value);
|
||||
|
||||
xml::Element* el;
|
||||
el = applicationEl->findChild({}, "activity");
|
||||
ASSERT_NE(nullptr, el);
|
||||
xml::Element* el;
|
||||
el = applicationEl->findChild({}, "activity");
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ(std::string("android.activity.Start"), attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ(std::string("android.activity.Start"), attr->value);
|
||||
|
||||
el = applicationEl->findChild({}, "receiver");
|
||||
ASSERT_NE(nullptr, el);
|
||||
el = applicationEl->findChild({}, "receiver");
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
|
||||
attr = el->findAttribute(xml::kSchemaAndroid, "name");
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
|
||||
ManifestFixerOptions options;
|
||||
options.renameInstrumentationTargetPackage = std::string("com.android");
|
||||
TEST_F(ManifestFixerTest,
|
||||
RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
|
||||
ManifestFixerOptions options;
|
||||
options.renameInstrumentationTargetPackage = std::string("com.android");
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<instrumentation android:targetPackage="android" />
|
||||
</manifest>)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
</manifest>)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
|
||||
xml::Element* instrumentationEl = manifestEl->findChild({}, "instrumentation");
|
||||
ASSERT_NE(nullptr, instrumentationEl);
|
||||
xml::Element* instrumentationEl =
|
||||
manifestEl->findChild({}, "instrumentation");
|
||||
ASSERT_NE(nullptr, instrumentationEl);
|
||||
|
||||
xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("com.android"), attr->value);
|
||||
xml::Attribute* attr =
|
||||
instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("com.android"), attr->value);
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
|
||||
ManifestFixerOptions options;
|
||||
options.versionNameDefault = std::string("Beta");
|
||||
options.versionCodeDefault = std::string("0x10000000");
|
||||
ManifestFixerOptions options;
|
||||
options.versionNameDefault = std::string("Beta");
|
||||
options.versionCodeDefault = std::string("0x10000000");
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android" />)EOF", options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
package="android" />)EOF",
|
||||
options);
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
xml::Element* manifestEl = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, manifestEl);
|
||||
|
||||
xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionName");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("Beta"), attr->value);
|
||||
xml::Attribute* attr =
|
||||
manifestEl->findAttribute(xml::kSchemaAndroid, "versionName");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("Beta"), attr->value);
|
||||
|
||||
attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("0x10000000"), attr->value);
|
||||
attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(std::string("0x10000000"), attr->value);
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
|
||||
EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"hello\" />"));
|
||||
EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
|
||||
EXPECT_EQ(nullptr,
|
||||
verify("<manifest package=\"android\" coreApp=\"hello\" />"));
|
||||
EXPECT_EQ(nullptr,
|
||||
verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
|
||||
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
verify("<manifest package=\"android\" coreApp=\"true\" />");
|
||||
ASSERT_NE(nullptr, doc);
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
verify("<manifest package=\"android\" coreApp=\"true\" />");
|
||||
ASSERT_NE(nullptr, doc);
|
||||
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
EXPECT_EQ("manifest", el->name);
|
||||
EXPECT_EQ("manifest", el->name);
|
||||
|
||||
xml::Attribute* attr = el->findAttribute("", "coreApp");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
xml::Attribute* attr = el->findAttribute("", "coreApp");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
|
||||
EXPECT_NE(nullptr, attr->compiledValue);
|
||||
EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get()));
|
||||
EXPECT_NE(nullptr, attr->compiledValue);
|
||||
EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get()));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -25,56 +25,61 @@ namespace aapt {
|
||||
template <typename InputContainer, typename OutputIterator, typename Predicate>
|
||||
OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result,
|
||||
Predicate pred) {
|
||||
const auto last = inputContainer.end();
|
||||
auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred);
|
||||
if (newEnd == last) {
|
||||
return result;
|
||||
}
|
||||
|
||||
*result = std::move(*newEnd);
|
||||
|
||||
auto first = newEnd;
|
||||
++first;
|
||||
|
||||
for (; first != last; ++first) {
|
||||
if (bool(pred(*first))) {
|
||||
// We want to move this guy
|
||||
*result = std::move(*first);
|
||||
++result;
|
||||
} else {
|
||||
// We want to keep this guy, but we will need to move it up the list to replace
|
||||
// missing items.
|
||||
*newEnd = std::move(*first);
|
||||
++newEnd;
|
||||
}
|
||||
}
|
||||
|
||||
inputContainer.erase(newEnd, last);
|
||||
const auto last = inputContainer.end();
|
||||
auto newEnd =
|
||||
std::find_if(inputContainer.begin(), inputContainer.end(), pred);
|
||||
if (newEnd == last) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
if (!type) {
|
||||
continue;
|
||||
}
|
||||
*result = std::move(*newEnd);
|
||||
|
||||
if (type->symbolStatus.state != SymbolState::kPublic) {
|
||||
// No public attributes, so we can safely leave these private attributes where they are.
|
||||
return true;
|
||||
}
|
||||
auto first = newEnd;
|
||||
++first;
|
||||
|
||||
ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate);
|
||||
assert(privAttrType->entries.empty());
|
||||
|
||||
moveIf(type->entries, std::back_inserter(privAttrType->entries),
|
||||
[](const std::unique_ptr<ResourceEntry>& entry) -> bool {
|
||||
return entry->symbolStatus.state != SymbolState::kPublic;
|
||||
});
|
||||
break;
|
||||
for (; first != last; ++first) {
|
||||
if (bool(pred(*first))) {
|
||||
// We want to move this guy
|
||||
*result = std::move(*first);
|
||||
++result;
|
||||
} else {
|
||||
// We want to keep this guy, but we will need to move it up the list to
|
||||
// replace
|
||||
// missing items.
|
||||
*newEnd = std::move(*first);
|
||||
++newEnd;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inputContainer.erase(newEnd, last);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
bool PrivateAttributeMover::consume(IAaptContext* context,
|
||||
ResourceTable* table) {
|
||||
for (auto& package : table->packages) {
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
if (!type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type->symbolStatus.state != SymbolState::kPublic) {
|
||||
// No public attributes, so we can safely leave these private attributes
|
||||
// where they are.
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceTableType* privAttrType =
|
||||
package->findOrCreateType(ResourceType::kAttrPrivate);
|
||||
assert(privAttrType->entries.empty());
|
||||
|
||||
moveIf(type->entries, std::back_inserter(privAttrType->entries),
|
||||
[](const std::unique_ptr<ResourceEntry>& entry) -> bool {
|
||||
return entry->symbolStatus.state != SymbolState::kPublic;
|
||||
});
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -20,56 +20,60 @@
|
||||
namespace aapt {
|
||||
|
||||
TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/publicA")
|
||||
.addSimple("android:attr/privateA")
|
||||
.addSimple("android:attr/publicB")
|
||||
.addSimple("android:attr/privateB")
|
||||
.setSymbolState("android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
|
||||
.setSymbolState("android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/publicA")
|
||||
.addSimple("android:attr/privateA")
|
||||
.addSimple("android:attr/publicB")
|
||||
.addSimple("android:attr/privateB")
|
||||
.setSymbolState("android:attr/publicA", ResourceId(0x01010000),
|
||||
SymbolState::kPublic)
|
||||
.setSymbolState("android:attr/publicB", ResourceId(0x01010000),
|
||||
SymbolState::kPublic)
|
||||
.build();
|
||||
|
||||
PrivateAttributeMover mover;
|
||||
ASSERT_TRUE(mover.consume(context.get(), table.get()));
|
||||
PrivateAttributeMover mover;
|
||||
ASSERT_TRUE(mover.consume(context.get(), table.get()));
|
||||
|
||||
ResourceTablePackage* package = table->findPackage("android");
|
||||
ASSERT_NE(package, nullptr);
|
||||
ResourceTablePackage* package = table->findPackage("android");
|
||||
ASSERT_NE(package, nullptr);
|
||||
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
EXPECT_NE(type->findEntry("publicA"), nullptr);
|
||||
EXPECT_NE(type->findEntry("publicB"), nullptr);
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
EXPECT_NE(type->findEntry("publicA"), nullptr);
|
||||
EXPECT_NE(type->findEntry("publicB"), nullptr);
|
||||
|
||||
type = package->findType(ResourceType::kAttrPrivate);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
EXPECT_NE(type->findEntry("privateA"), nullptr);
|
||||
EXPECT_NE(type->findEntry("privateB"), nullptr);
|
||||
type = package->findType(ResourceType::kAttrPrivate);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
EXPECT_NE(type->findEntry("privateA"), nullptr);
|
||||
EXPECT_NE(type->findEntry("privateB"), nullptr);
|
||||
}
|
||||
|
||||
TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
TEST(PrivateAttributeMoverTest,
|
||||
LeavePrivateAttributesWhenNoPublicAttributesDefined) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/privateA")
|
||||
.addSimple("android:attr/privateB")
|
||||
.build();
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addSimple("android:attr/privateA")
|
||||
.addSimple("android:attr/privateB")
|
||||
.build();
|
||||
|
||||
PrivateAttributeMover mover;
|
||||
ASSERT_TRUE(mover.consume(context.get(), table.get()));
|
||||
PrivateAttributeMover mover;
|
||||
ASSERT_TRUE(mover.consume(context.get(), table.get()));
|
||||
|
||||
ResourceTablePackage* package = table->findPackage("android");
|
||||
ASSERT_NE(package, nullptr);
|
||||
ResourceTablePackage* package = table->findPackage("android");
|
||||
ASSERT_NE(package, nullptr);
|
||||
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
ResourceTableType* type = package->findType(ResourceType::kAttr);
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_EQ(type->entries.size(), 2u);
|
||||
|
||||
type = package->findType(ResourceType::kAttrPrivate);
|
||||
ASSERT_EQ(type, nullptr);
|
||||
type = package->findType(ResourceType::kAttrPrivate);
|
||||
ASSERT_EQ(type, nullptr);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -18,101 +18,103 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ProductFilter::ResourceConfigValueIter
|
||||
ProductFilter::selectProductToKeep(const ResourceNameRef& name,
|
||||
const ResourceConfigValueIter begin,
|
||||
const ResourceConfigValueIter end,
|
||||
IDiagnostics* diag) {
|
||||
ResourceConfigValueIter defaultProductIter = end;
|
||||
ResourceConfigValueIter selectedProductIter = end;
|
||||
ProductFilter::ResourceConfigValueIter ProductFilter::selectProductToKeep(
|
||||
const ResourceNameRef& name, const ResourceConfigValueIter begin,
|
||||
const ResourceConfigValueIter end, IDiagnostics* diag) {
|
||||
ResourceConfigValueIter defaultProductIter = end;
|
||||
ResourceConfigValueIter selectedProductIter = end;
|
||||
|
||||
for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
|
||||
ResourceConfigValue* configValue = iter->get();
|
||||
if (mProducts.find(configValue->product) != mProducts.end()) {
|
||||
if (selectedProductIter != end) {
|
||||
// We have two possible values for this product!
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "selection of product '" << configValue->product
|
||||
<< "' for resource " << name << " is ambiguous");
|
||||
for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
|
||||
ResourceConfigValue* configValue = iter->get();
|
||||
if (mProducts.find(configValue->product) != mProducts.end()) {
|
||||
if (selectedProductIter != end) {
|
||||
// We have two possible values for this product!
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "selection of product '" << configValue->product
|
||||
<< "' for resource " << name << " is ambiguous");
|
||||
|
||||
ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get();
|
||||
diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource())
|
||||
<< "product '" << previouslySelectedConfigValue->product
|
||||
<< "' is also a candidate");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Select this product.
|
||||
selectedProductIter = iter;
|
||||
}
|
||||
|
||||
if (configValue->product.empty() || configValue->product == "default") {
|
||||
if (defaultProductIter != end) {
|
||||
// We have two possible default values.
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "multiple default products defined for resource " << name);
|
||||
|
||||
ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
|
||||
diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
|
||||
<< "default product also defined here");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Mark the default.
|
||||
defaultProductIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProductIter == end) {
|
||||
diag->error(DiagMessage() << "no default product defined for resource " << name);
|
||||
ResourceConfigValue* previouslySelectedConfigValue =
|
||||
selectedProductIter->get();
|
||||
diag->note(
|
||||
DiagMessage(previouslySelectedConfigValue->value->getSource())
|
||||
<< "product '" << previouslySelectedConfigValue->product
|
||||
<< "' is also a candidate");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Select this product.
|
||||
selectedProductIter = iter;
|
||||
}
|
||||
|
||||
if (selectedProductIter == end) {
|
||||
selectedProductIter = defaultProductIter;
|
||||
if (configValue->product.empty() || configValue->product == "default") {
|
||||
if (defaultProductIter != end) {
|
||||
// We have two possible default values.
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "multiple default products defined for resource "
|
||||
<< name);
|
||||
|
||||
ResourceConfigValue* previouslyDefaultConfigValue =
|
||||
defaultProductIter->get();
|
||||
diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
|
||||
<< "default product also defined here");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Mark the default.
|
||||
defaultProductIter = iter;
|
||||
}
|
||||
return selectedProductIter;
|
||||
}
|
||||
|
||||
if (defaultProductIter == end) {
|
||||
diag->error(DiagMessage() << "no default product defined for resource "
|
||||
<< name);
|
||||
return end;
|
||||
}
|
||||
|
||||
if (selectedProductIter == end) {
|
||||
selectedProductIter = defaultProductIter;
|
||||
}
|
||||
return selectedProductIter;
|
||||
}
|
||||
|
||||
bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) {
|
||||
bool error = false;
|
||||
for (auto& pkg : table->packages) {
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
|
||||
bool error = false;
|
||||
for (auto& pkg : table->packages) {
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
|
||||
|
||||
ResourceConfigValueIter iter = entry->values.begin();
|
||||
ResourceConfigValueIter startRangeIter = iter;
|
||||
while (iter != entry->values.end()) {
|
||||
++iter;
|
||||
if (iter == entry->values.end() ||
|
||||
(*iter)->config != (*startRangeIter)->config) {
|
||||
|
||||
// End of the array, or we saw a different config,
|
||||
// so this must be the end of a range of products.
|
||||
// Select the product to keep from the set of products defined.
|
||||
ResourceNameRef name(pkg->name, type->type, entry->name);
|
||||
auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
|
||||
context->getDiagnostics());
|
||||
if (valueToKeep == iter) {
|
||||
// An error occurred, we could not pick a product.
|
||||
error = true;
|
||||
} else {
|
||||
// We selected a product to keep. Move it to the new array.
|
||||
newValues.push_back(std::move(*valueToKeep));
|
||||
}
|
||||
|
||||
// Start the next range of products.
|
||||
startRangeIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Now move the new values in to place.
|
||||
entry->values = std::move(newValues);
|
||||
ResourceConfigValueIter iter = entry->values.begin();
|
||||
ResourceConfigValueIter startRangeIter = iter;
|
||||
while (iter != entry->values.end()) {
|
||||
++iter;
|
||||
if (iter == entry->values.end() ||
|
||||
(*iter)->config != (*startRangeIter)->config) {
|
||||
// End of the array, or we saw a different config,
|
||||
// so this must be the end of a range of products.
|
||||
// Select the product to keep from the set of products defined.
|
||||
ResourceNameRef name(pkg->name, type->type, entry->name);
|
||||
auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
|
||||
context->getDiagnostics());
|
||||
if (valueToKeep == iter) {
|
||||
// An error occurred, we could not pick a product.
|
||||
error = true;
|
||||
} else {
|
||||
// We selected a product to keep. Move it to the new array.
|
||||
newValues.push_back(std::move(*valueToKeep));
|
||||
}
|
||||
|
||||
// Start the next range of products.
|
||||
startRangeIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Now move the new values in to place.
|
||||
entry->values = std::move(newValues);
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user