Merge "AAPT2: Rename to match new style"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b629f36601
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <utils/ByteOrder.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
|
||||
#include <utils/Compat.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/FileMap.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class FileMap;
|
||||
|
||||
/*
|
||||
* Instances of this class provide read-only operations on a byte stream.
|
||||
*
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <androidfw/Asset.h>
|
||||
#include <androidfw/LocaleData.h>
|
||||
#include <utils/ByteOrder.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define __TYPE_WRAPPERS_H
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <utils/ByteOrder.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 100
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ hostStaticLibs_windows := libz
|
||||
hostLdLibs_linux := -lz
|
||||
hostLdLibs_darwin := -lz
|
||||
|
||||
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
|
||||
cFlags := -Wall -Werror -Wno-unused-parameter
|
||||
cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
|
||||
cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
|
||||
cppFlags := -Wno-missing-field-initializers -fno-exceptions -fno-rtti
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#ifndef AAPT_APP_INFO_H
|
||||
#define AAPT_APP_INFO_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/Maybe.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -36,17 +36,17 @@ struct AppInfo {
|
||||
/**
|
||||
* The App's minimum SDK version.
|
||||
*/
|
||||
Maybe<std::string> minSdkVersion;
|
||||
Maybe<std::string> min_sdk_version;
|
||||
|
||||
/**
|
||||
* The Version code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> versionCode;
|
||||
Maybe<uint32_t> version_code;
|
||||
|
||||
/**
|
||||
* The revision code of the app.
|
||||
*/
|
||||
Maybe<uint32_t> revisionCode;
|
||||
Maybe<uint32_t> revision_code;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,22 +15,24 @@
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "Locale.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::ResTable_config;
|
||||
|
||||
static const char* kWildcardName = "any";
|
||||
|
||||
const ConfigDescription& ConfigDescription::defaultConfig() {
|
||||
const ConfigDescription& ConfigDescription::DefaultConfig() {
|
||||
static ConfigDescription config = {};
|
||||
return config;
|
||||
}
|
||||
@@ -589,169 +591,169 @@ static bool parseVersion(const char* name, ResTable_config* out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(str, '-');
|
||||
bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
|
||||
std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
|
||||
|
||||
ConfigDescription config;
|
||||
ssize_t partsConsumed = 0;
|
||||
ssize_t parts_consumed = 0;
|
||||
LocaleValue locale;
|
||||
|
||||
const auto partsEnd = parts.end();
|
||||
auto partIter = parts.begin();
|
||||
const auto parts_end = parts.end();
|
||||
auto part_iter = parts.begin();
|
||||
|
||||
if (str.size() == 0) {
|
||||
goto success;
|
||||
}
|
||||
|
||||
if (parseMcc(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseMcc(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseMnc(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseMnc(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
// Locale spans a few '-' separators, so we let it
|
||||
// control the index.
|
||||
partsConsumed = locale.initFromParts(partIter, partsEnd);
|
||||
if (partsConsumed < 0) {
|
||||
parts_consumed = locale.InitFromParts(part_iter, parts_end);
|
||||
if (parts_consumed < 0) {
|
||||
return false;
|
||||
} else {
|
||||
locale.writeTo(&config);
|
||||
partIter += partsConsumed;
|
||||
if (partIter == partsEnd) {
|
||||
locale.WriteTo(&config);
|
||||
part_iter += parts_consumed;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseLayoutDirection(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseLayoutDirection(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseSmallestScreenWidthDp(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenWidthDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenWidthDp(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenHeightDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenHeightDp(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenLayoutSize(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenLayoutSize(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenLayoutLong(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenLayoutLong(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenRound(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenRound(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseOrientation(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseOrientation(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseUiModeType(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseUiModeType(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseUiModeNight(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseUiModeNight(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseDensity(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseDensity(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseTouchscreen(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseTouchscreen(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseKeysHidden(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseKeysHidden(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseKeyboard(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseKeyboard(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseNavHidden(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseNavHidden(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseNavigation(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseNavigation(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenSize(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseScreenSize(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseVersion(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
if (parseVersion(part_iter->c_str(), &config)) {
|
||||
++part_iter;
|
||||
if (part_iter == parts_end) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
@@ -761,57 +763,57 @@ bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
|
||||
|
||||
success:
|
||||
if (out != NULL) {
|
||||
applyVersionForCompatibility(&config);
|
||||
ApplyVersionForCompatibility(&config);
|
||||
*out = config;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigDescription::applyVersionForCompatibility(
|
||||
void ConfigDescription::ApplyVersionForCompatibility(
|
||||
ConfigDescription* config) {
|
||||
uint16_t minSdk = 0;
|
||||
uint16_t min_sdk = 0;
|
||||
if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
|
||||
minSdk = SDK_MARSHMALLOW;
|
||||
min_sdk = SDK_MARSHMALLOW;
|
||||
} else if (config->density == ResTable_config::DENSITY_ANY) {
|
||||
minSdk = SDK_LOLLIPOP;
|
||||
min_sdk = SDK_LOLLIPOP;
|
||||
} else if (config->smallestScreenWidthDp !=
|
||||
ResTable_config::SCREENWIDTH_ANY ||
|
||||
config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY ||
|
||||
config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
|
||||
minSdk = SDK_HONEYCOMB_MR2;
|
||||
min_sdk = SDK_HONEYCOMB_MR2;
|
||||
} else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) !=
|
||||
ResTable_config::UI_MODE_TYPE_ANY ||
|
||||
(config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) !=
|
||||
ResTable_config::UI_MODE_NIGHT_ANY) {
|
||||
minSdk = SDK_FROYO;
|
||||
min_sdk = SDK_FROYO;
|
||||
} else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) !=
|
||||
ResTable_config::SCREENSIZE_ANY ||
|
||||
(config->screenLayout & ResTable_config::MASK_SCREENLONG) !=
|
||||
ResTable_config::SCREENLONG_ANY ||
|
||||
config->density != ResTable_config::DENSITY_DEFAULT) {
|
||||
minSdk = SDK_DONUT;
|
||||
min_sdk = SDK_DONUT;
|
||||
}
|
||||
|
||||
if (minSdk > config->sdkVersion) {
|
||||
config->sdkVersion = minSdk;
|
||||
if (min_sdk > config->sdkVersion) {
|
||||
config->sdkVersion = min_sdk;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigDescription ConfigDescription::copyWithoutSdkVersion() const {
|
||||
ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const {
|
||||
ConfigDescription copy = *this;
|
||||
copy.sdkVersion = 0;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool ConfigDescription::dominates(const ConfigDescription& o) const {
|
||||
if (*this == defaultConfig() || *this == o) {
|
||||
bool ConfigDescription::Dominates(const ConfigDescription& o) const {
|
||||
if (*this == DefaultConfig() || *this == o) {
|
||||
return true;
|
||||
}
|
||||
return matchWithDensity(o) && !o.matchWithDensity(*this) &&
|
||||
!isMoreSpecificThan(o) && !o.hasHigherPrecedenceThan(*this);
|
||||
return MatchWithDensity(o) && !o.MatchWithDensity(*this) &&
|
||||
!isMoreSpecificThan(o) && !o.HasHigherPrecedenceThan(*this);
|
||||
}
|
||||
|
||||
bool ConfigDescription::hasHigherPrecedenceThan(
|
||||
bool ConfigDescription::HasHigherPrecedenceThan(
|
||||
const ConfigDescription& o) const {
|
||||
// The order of the following tests defines the importance of one
|
||||
// configuration parameter over another. Those tests first are more
|
||||
@@ -866,7 +868,7 @@ bool ConfigDescription::hasHigherPrecedenceThan(
|
||||
return *this != o;
|
||||
}
|
||||
|
||||
bool ConfigDescription::conflictsWith(const ConfigDescription& o) const {
|
||||
bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {
|
||||
// This method should be updated as new configuration parameters are
|
||||
// introduced (e.g. screenConfig2).
|
||||
auto pred = [](const uint32_t a, const uint32_t b) -> bool {
|
||||
@@ -892,8 +894,8 @@ bool ConfigDescription::conflictsWith(const ConfigDescription& o) const {
|
||||
!pred(keyboard, o.keyboard) || !pred(navigation, o.navigation);
|
||||
}
|
||||
|
||||
bool ConfigDescription::isCompatibleWith(const ConfigDescription& o) const {
|
||||
return !conflictsWith(o) && !dominates(o) && !o.dominates(*this);
|
||||
bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const {
|
||||
return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
#ifndef AAPT_CONFIG_DESCRIPTION_H
|
||||
#define AAPT_CONFIG_DESCRIPTION_H
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <ostream>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
@@ -32,7 +33,7 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
/**
|
||||
* Returns an immutable default config.
|
||||
*/
|
||||
static const ConfigDescription& defaultConfig();
|
||||
static const ConfigDescription& DefaultConfig();
|
||||
|
||||
/*
|
||||
* Parse a string of the form 'fr-sw600dp-land' and fill in the
|
||||
@@ -41,14 +42,14 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
* The resulting configuration has the appropriate sdkVersion defined
|
||||
* for backwards compatibility.
|
||||
*/
|
||||
static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
|
||||
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);
|
||||
static void ApplyVersionForCompatibility(ConfigDescription* config);
|
||||
|
||||
ConfigDescription();
|
||||
ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit)
|
||||
@@ -59,7 +60,7 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
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
|
||||
@@ -70,14 +71,14 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
* 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;
|
||||
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;
|
||||
bool HasHigherPrecedenceThan(const ConfigDescription& o) const;
|
||||
|
||||
/**
|
||||
* A configuration conflicts with another configuration if both
|
||||
@@ -85,7 +86,7 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
* 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;
|
||||
bool ConflictsWith(const ConfigDescription& o) const;
|
||||
|
||||
/**
|
||||
* A configuration is compatible with another configuration if both
|
||||
@@ -93,9 +94,9 @@ struct ConfigDescription : public android::ResTable_config {
|
||||
* 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 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;
|
||||
@@ -141,7 +142,7 @@ inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::matchWithDensity(
|
||||
inline bool ConfigDescription::MatchWithDensity(
|
||||
const ConfigDescription& o) const {
|
||||
return match(o) && (density == 0 || density == o.density);
|
||||
}
|
||||
|
||||
@@ -15,17 +15,18 @@
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "SdkConstants.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestParse(
|
||||
const StringPiece& input, ConfigDescription* config = nullptr) {
|
||||
if (ConfigDescription::parse(input, config)) {
|
||||
if (ConfigDescription::Parse(input, config)) {
|
||||
return ::testing::AssertionSuccess() << input << " was successfully parsed";
|
||||
}
|
||||
return ::testing::AssertionFailure() << input << " could not be parsed";
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
|
||||
#include "Debug.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -28,18 +24,25 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class PrintVisitor : public ValueVisitor {
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
using ValueVisitor::Visit;
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
void Visit(Attribute* attr) override {
|
||||
std::cout << "(attr) type=";
|
||||
attr->printMask(&std::cout);
|
||||
attr->PrintMask(&std::cout);
|
||||
static constexpr uint32_t kMask =
|
||||
android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr->typeMask & kMask) {
|
||||
if (attr->type_mask & kMask) {
|
||||
for (const auto& symbol : attr->symbols) {
|
||||
std::cout << "\n " << symbol.symbol.name.value().entry;
|
||||
if (symbol.symbol.id) {
|
||||
@@ -50,20 +53,20 @@ class PrintVisitor : public ValueVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
void Visit(Style* style) override {
|
||||
std::cout << "(style)";
|
||||
if (style->parent) {
|
||||
const Reference& parentRef = style->parent.value();
|
||||
const Reference& parent_ref = style->parent.value();
|
||||
std::cout << " parent=";
|
||||
if (parentRef.name) {
|
||||
if (parentRef.privateReference) {
|
||||
if (parent_ref.name) {
|
||||
if (parent_ref.private_reference) {
|
||||
std::cout << "*";
|
||||
}
|
||||
std::cout << parentRef.name.value() << " ";
|
||||
std::cout << parent_ref.name.value() << " ";
|
||||
}
|
||||
|
||||
if (parentRef.id) {
|
||||
std::cout << parentRef.id.value();
|
||||
if (parent_ref.id) {
|
||||
std::cout << parent_ref.id.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +88,11 @@ class PrintVisitor : public ValueVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
void Visit(Styleable* styleable) override {
|
||||
std::cout << "(styleable)";
|
||||
for (const auto& attr : styleable->entries) {
|
||||
std::cout << "\n ";
|
||||
@@ -107,10 +110,10 @@ class PrintVisitor : public ValueVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
void visitItem(Item* item) override { item->print(&std::cout); }
|
||||
void VisitItem(Item* item) override { item->Print(&std::cout); }
|
||||
};
|
||||
|
||||
void Debug::printTable(ResourceTable* table,
|
||||
void Debug::PrintTable(ResourceTable* table,
|
||||
const DebugPrintTableOptions& options) {
|
||||
PrintVisitor visitor;
|
||||
|
||||
@@ -128,10 +131,10 @@ void Debug::printTable(ResourceTable* table,
|
||||
}
|
||||
std::cout << " entryCount=" << type->entries.size() << std::endl;
|
||||
|
||||
std::vector<const ResourceEntry*> sortedEntries;
|
||||
std::vector<const ResourceEntry*> sorted_entries;
|
||||
for (const auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(
|
||||
sortedEntries.begin(), sortedEntries.end(), entry.get(),
|
||||
sorted_entries.begin(), sorted_entries.end(), entry.get(),
|
||||
[](const ResourceEntry* a, const ResourceEntry* b) -> bool {
|
||||
if (a->id && b->id) {
|
||||
return a->id.value() < b->id.value();
|
||||
@@ -141,17 +144,17 @@ void Debug::printTable(ResourceTable* table,
|
||||
return false;
|
||||
}
|
||||
});
|
||||
sortedEntries.insert(iter, entry.get());
|
||||
sorted_entries.insert(iter, entry.get());
|
||||
}
|
||||
|
||||
for (const ResourceEntry* entry : sortedEntries) {
|
||||
for (const ResourceEntry* entry : sorted_entries) {
|
||||
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) {
|
||||
switch (entry->symbol_status.state) {
|
||||
case SymbolState::kPublic:
|
||||
std::cout << " PUBLIC";
|
||||
break;
|
||||
@@ -166,9 +169,9 @@ void Debug::printTable(ResourceTable* table,
|
||||
|
||||
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();
|
||||
value->value->Accept(&visitor);
|
||||
if (options.show_sources && !value->value->GetSource().path.empty()) {
|
||||
std::cout << " src=" << value->value->GetSource();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@@ -177,35 +180,36 @@ void Debug::printTable(ResourceTable* table,
|
||||
}
|
||||
}
|
||||
|
||||
static size_t getNodeIndex(const std::vector<ResourceName>& names,
|
||||
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);
|
||||
CHECK(iter != names.end());
|
||||
CHECK(*iter == name);
|
||||
return std::distance(names.begin(), iter);
|
||||
}
|
||||
|
||||
void Debug::printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle) {
|
||||
void Debug::PrintStyleGraph(ResourceTable* table,
|
||||
const ResourceName& target_style) {
|
||||
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];
|
||||
std::queue<ResourceName> styles_to_visit;
|
||||
styles_to_visit.push(target_style);
|
||||
for (; !styles_to_visit.empty(); styles_to_visit.pop()) {
|
||||
const ResourceName& style_name = styles_to_visit.front();
|
||||
std::set<ResourceName>& parents = graph[style_name];
|
||||
if (!parents.empty()) {
|
||||
// We've already visited this style.
|
||||
continue;
|
||||
}
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(styleName);
|
||||
Maybe<ResourceTable::SearchResult> result = table->FindResource(style_name);
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
for (const auto& value : entry->values) {
|
||||
if (Style* style = valueCast<Style>(value->value.get())) {
|
||||
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());
|
||||
styles_to_visit.push(style->parent.value().name.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,24 +223,24 @@ void Debug::printStyleGraph(ResourceTable* table,
|
||||
|
||||
std::cout << "digraph styles {\n";
|
||||
for (const auto& name : names) {
|
||||
std::cout << " node_" << getNodeIndex(names, name) << " [label=\"" << name
|
||||
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);
|
||||
const ResourceName& style_name = entry.first;
|
||||
size_t style_node_index = GetNodeIndex(names, style_name);
|
||||
|
||||
for (const auto& parentName : entry.second) {
|
||||
std::cout << " node_" << styleNodeIndex << " -> "
|
||||
<< "node_" << getNodeIndex(names, parentName) << ";\n";
|
||||
for (const auto& parent_name : entry.second) {
|
||||
std::cout << " node_" << style_node_index << " -> "
|
||||
<< "node_" << GetNodeIndex(names, parent_name) << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "}" << std::endl;
|
||||
}
|
||||
|
||||
void Debug::dumpHex(const void* data, size_t len) {
|
||||
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]
|
||||
@@ -255,55 +259,55 @@ namespace {
|
||||
|
||||
class XmlPrinter : public xml::Visitor {
|
||||
public:
|
||||
using xml::Visitor::visit;
|
||||
using xml::Visitor::Visit;
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
std::cerr << mPrefix;
|
||||
void Visit(xml::Element* el) override {
|
||||
std::cerr << prefix_;
|
||||
std::cerr << "E: ";
|
||||
if (!el->namespaceUri.empty()) {
|
||||
std::cerr << el->namespaceUri << ":";
|
||||
if (!el->namespace_uri.empty()) {
|
||||
std::cerr << el->namespace_uri << ":";
|
||||
}
|
||||
std::cerr << el->name << " (line=" << el->lineNumber << ")\n";
|
||||
std::cerr << el->name << " (line=" << el->line_number << ")\n";
|
||||
|
||||
for (const xml::Attribute& attr : el->attributes) {
|
||||
std::cerr << mPrefix << " A: ";
|
||||
if (!attr.namespaceUri.empty()) {
|
||||
std::cerr << attr.namespaceUri << ":";
|
||||
std::cerr << prefix_ << " A: ";
|
||||
if (!attr.namespace_uri.empty()) {
|
||||
std::cerr << attr.namespace_uri << ":";
|
||||
}
|
||||
std::cerr << attr.name << "=" << attr.value << "\n";
|
||||
}
|
||||
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(el);
|
||||
mPrefix.resize(previousSize);
|
||||
const size_t previous_size = prefix_.size();
|
||||
prefix_ += " ";
|
||||
xml::Visitor::Visit(el);
|
||||
prefix_.resize(previous_size);
|
||||
}
|
||||
|
||||
void visit(xml::Namespace* ns) override {
|
||||
std::cerr << mPrefix;
|
||||
std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri
|
||||
<< " (line=" << ns->lineNumber << ")\n";
|
||||
void Visit(xml::Namespace* ns) override {
|
||||
std::cerr << prefix_;
|
||||
std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri
|
||||
<< " (line=" << ns->line_number << ")\n";
|
||||
|
||||
const size_t previousSize = mPrefix.size();
|
||||
mPrefix += " ";
|
||||
xml::Visitor::visit(ns);
|
||||
mPrefix.resize(previousSize);
|
||||
const size_t previous_size = prefix_.size();
|
||||
prefix_ += " ";
|
||||
xml::Visitor::Visit(ns);
|
||||
prefix_.resize(previous_size);
|
||||
}
|
||||
|
||||
void visit(xml::Text* text) override {
|
||||
std::cerr << mPrefix;
|
||||
void Visit(xml::Text* text) override {
|
||||
std::cerr << prefix_;
|
||||
std::cerr << "T: '" << text->text << "'\n";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mPrefix;
|
||||
std::string prefix_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void Debug::dumpXml(xml::XmlResource* doc) {
|
||||
void Debug::DumpXml(xml::XmlResource* doc) {
|
||||
XmlPrinter printer;
|
||||
doc->root->accept(&printer);
|
||||
doc->root->Accept(&printer);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,26 +17,26 @@
|
||||
#ifndef AAPT_DEBUG_H
|
||||
#define AAPT_DEBUG_H
|
||||
|
||||
// Include for printf-like debugging.
|
||||
#include <iostream>
|
||||
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
// Include for printf-like debugging.
|
||||
#include <iostream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct DebugPrintTableOptions {
|
||||
bool showSources = false;
|
||||
bool show_sources = false;
|
||||
};
|
||||
|
||||
struct Debug {
|
||||
static void printTable(ResourceTable* table,
|
||||
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 PrintStyleGraph(ResourceTable* table,
|
||||
const ResourceName& target_style);
|
||||
static void DumpHex(const void* data, size_t len);
|
||||
static void DumpXml(xml::XmlResource* doc);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
#ifndef AAPT_DIAGNOSTICS_H
|
||||
#define AAPT_DIAGNOSTICS_H
|
||||
|
||||
#include "Source.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Source.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct DiagMessageActual {
|
||||
@@ -34,28 +35,28 @@ struct DiagMessageActual {
|
||||
};
|
||||
|
||||
struct DiagMessage {
|
||||
private:
|
||||
Source mSource;
|
||||
std::stringstream mMessage;
|
||||
|
||||
public:
|
||||
DiagMessage() = default;
|
||||
|
||||
explicit DiagMessage(const StringPiece& src) : mSource(src) {}
|
||||
explicit DiagMessage(const StringPiece& src) : source_(src) {}
|
||||
|
||||
explicit DiagMessage(const Source& src) : mSource(src) {}
|
||||
explicit DiagMessage(const Source& src) : source_(src) {}
|
||||
|
||||
explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {}
|
||||
explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {}
|
||||
|
||||
template <typename T>
|
||||
DiagMessage& operator<<(const T& value) {
|
||||
mMessage << value;
|
||||
message_ << value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DiagMessageActual build() const {
|
||||
return DiagMessageActual{mSource, mMessage.str()};
|
||||
DiagMessageActual Build() const {
|
||||
return DiagMessageActual{source_, message_.str()};
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_;
|
||||
std::stringstream message_;
|
||||
};
|
||||
|
||||
struct IDiagnostics {
|
||||
@@ -63,21 +64,21 @@ struct IDiagnostics {
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,13 +86,13 @@ class StdErrDiagnostics : public IDiagnostics {
|
||||
public:
|
||||
StdErrDiagnostics() = default;
|
||||
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
void Log(Level level, DiagMessageActual& actual_msg) override {
|
||||
const char* tag;
|
||||
|
||||
switch (level) {
|
||||
case Level::Error:
|
||||
mNumErrors++;
|
||||
if (mNumErrors > 20) {
|
||||
num_errors_++;
|
||||
if (num_errors_ > 20) {
|
||||
return;
|
||||
}
|
||||
tag = "error";
|
||||
@@ -106,14 +107,14 @@ class StdErrDiagnostics : public IDiagnostics {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!actualMsg.source.path.empty()) {
|
||||
std::cerr << actualMsg.source << ": ";
|
||||
if (!actual_msg.source.path.empty()) {
|
||||
std::cerr << actual_msg.source << ": ";
|
||||
}
|
||||
std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
|
||||
std::cerr << tag << ": " << actual_msg.message << "." << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mNumErrors = 0;
|
||||
size_t num_errors_ = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
|
||||
};
|
||||
@@ -121,16 +122,16 @@ class StdErrDiagnostics : public IDiagnostics {
|
||||
class SourcePathDiagnostics : public IDiagnostics {
|
||||
public:
|
||||
SourcePathDiagnostics(const Source& src, IDiagnostics* diag)
|
||||
: mSource(src), mDiag(diag) {}
|
||||
: source_(src), diag_(diag) {}
|
||||
|
||||
void log(Level level, DiagMessageActual& actualMsg) override {
|
||||
actualMsg.source.path = mSource.path;
|
||||
mDiag->log(level, actualMsg);
|
||||
void Log(Level level, DiagMessageActual& actual_msg) override {
|
||||
actual_msg.source.path = source_.path;
|
||||
diag_->Log(level, actual_msg);
|
||||
}
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
IDiagnostics* mDiag;
|
||||
Source source_;
|
||||
IDiagnostics* diag_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
|
||||
};
|
||||
|
||||
@@ -15,77 +15,80 @@
|
||||
*/
|
||||
|
||||
#include "DominatorTree.h"
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
DominatorTree::DominatorTree(
|
||||
const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) {
|
||||
for (const auto& config : configs) {
|
||||
mProductRoots[config->product].tryAddChild(
|
||||
product_roots_[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);
|
||||
void DominatorTree::Accept(Visitor* visitor) {
|
||||
for (auto& entry : product_roots_) {
|
||||
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())) {
|
||||
bool DominatorTree::Node::TryAddChild(std::unique_ptr<Node> new_child) {
|
||||
CHECK(new_child->value_) << "cannot add a root or empty node as a child";
|
||||
if (value_ && !Dominates(new_child.get())) {
|
||||
// This is not the root and the child dominates us.
|
||||
return false;
|
||||
}
|
||||
return addChild(std::move(newChild));
|
||||
return AddChild(std::move(new_child));
|
||||
}
|
||||
|
||||
bool DominatorTree::Node::addChild(std::unique_ptr<Node> newChild) {
|
||||
bool hasDominatedChildren = false;
|
||||
bool DominatorTree::Node::AddChild(std::unique_ptr<Node> new_child) {
|
||||
bool has_dominated_children = 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));
|
||||
for (auto& child : children_) {
|
||||
if (new_child->Dominates(child.get())) {
|
||||
child->parent_ = new_child.get();
|
||||
new_child->children_.push_back(std::move(child));
|
||||
child = {};
|
||||
hasDominatedChildren = true;
|
||||
has_dominated_children = true;
|
||||
}
|
||||
}
|
||||
// Remove dominated children.
|
||||
if (hasDominatedChildren) {
|
||||
mChildren.erase(
|
||||
std::remove_if(mChildren.begin(), mChildren.end(),
|
||||
if (has_dominated_children) {
|
||||
children_.erase(
|
||||
std::remove_if(children_.begin(), children_.end(),
|
||||
[](const std::unique_ptr<Node>& child) -> bool {
|
||||
return child == nullptr;
|
||||
}),
|
||||
mChildren.end());
|
||||
children_.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));
|
||||
for (auto& child : children_) {
|
||||
if (child->Dominates(new_child.get())) {
|
||||
child->AddChild(std::move(new_child));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// The new config is not dominated by a child, so add it here.
|
||||
newChild->mParent = this;
|
||||
mChildren.push_back(std::move(newChild));
|
||||
new_child->parent_ = this;
|
||||
children_.push_back(std::move(new_child));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DominatorTree::Node::dominates(const Node* other) const {
|
||||
bool DominatorTree::Node::Dominates(const Node* other) const {
|
||||
// Check root node dominations.
|
||||
if (other->isRootNode()) {
|
||||
return isRootNode();
|
||||
} else if (isRootNode()) {
|
||||
if (other->is_root_node()) {
|
||||
return is_root_node();
|
||||
} else if (is_root_node()) {
|
||||
return true;
|
||||
}
|
||||
// Neither node is a root node; compare the configurations.
|
||||
return mValue->config.dominates(other->mValue->config);
|
||||
return value_->config.Dominates(other->value_->config);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
#ifndef AAPT_DOMINATOR_TREE_H
|
||||
#define AAPT_DOMINATOR_TREE_H
|
||||
|
||||
#include "ResourceTable.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ResourceTable.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -53,67 +53,67 @@ class DominatorTree {
|
||||
class Node {
|
||||
public:
|
||||
explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr)
|
||||
: mValue(value), mParent(parent) {}
|
||||
: value_(value), parent_(parent) {}
|
||||
|
||||
inline ResourceConfigValue* value() const { return mValue; }
|
||||
inline ResourceConfigValue* value() const { return value_; }
|
||||
|
||||
inline Node* parent() const { return mParent; }
|
||||
inline Node* parent() const { return parent_; }
|
||||
|
||||
inline bool isRootNode() const { return !mValue; }
|
||||
inline bool is_root_node() const { return !value_; }
|
||||
|
||||
inline const std::vector<std::unique_ptr<Node>>& children() const {
|
||||
return mChildren;
|
||||
return children_;
|
||||
}
|
||||
|
||||
bool tryAddChild(std::unique_ptr<Node> newChild);
|
||||
bool TryAddChild(std::unique_ptr<Node> new_child);
|
||||
|
||||
private:
|
||||
bool addChild(std::unique_ptr<Node> newChild);
|
||||
bool dominates(const Node* other) const;
|
||||
bool AddChild(std::unique_ptr<Node> new_child);
|
||||
bool Dominates(const Node* other) const;
|
||||
|
||||
ResourceConfigValue* mValue;
|
||||
Node* mParent;
|
||||
std::vector<std::unique_ptr<Node>> mChildren;
|
||||
ResourceConfigValue* value_;
|
||||
Node* parent_;
|
||||
std::vector<std::unique_ptr<Node>> children_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Node);
|
||||
};
|
||||
|
||||
struct Visitor {
|
||||
virtual ~Visitor() = default;
|
||||
virtual void visitTree(const std::string& product, Node* root) = 0;
|
||||
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 {
|
||||
void VisitTree(const std::string& product, Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get());
|
||||
VisitNode(child.get());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void visitConfig(Node* node) = 0;
|
||||
virtual void VisitConfig(Node* node) = 0;
|
||||
|
||||
private:
|
||||
void visitNode(Node* node) {
|
||||
void VisitNode(Node* node) {
|
||||
for (auto& child : node->children()) {
|
||||
visitNode(child.get());
|
||||
VisitNode(child.get());
|
||||
}
|
||||
visitConfig(node);
|
||||
VisitConfig(node);
|
||||
}
|
||||
};
|
||||
|
||||
void accept(Visitor* visitor);
|
||||
void Accept(Visitor* visitor);
|
||||
|
||||
inline const std::map<std::string, Node>& getProductRoots() const {
|
||||
return mProductRoots;
|
||||
inline const std::map<std::string, Node>& product_roots() const {
|
||||
return product_roots_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DominatorTree);
|
||||
|
||||
std::map<std::string, Node> mProductRoots;
|
||||
std::map<std::string, Node> product_roots_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,67 +15,68 @@
|
||||
*/
|
||||
|
||||
#include "DominatorTree.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
class PrettyPrinter : public DominatorTree::Visitor {
|
||||
public:
|
||||
explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {}
|
||||
explicit PrettyPrinter(const int indent = 2) : indent_(indent) {}
|
||||
|
||||
void visitTree(const std::string& product,
|
||||
void VisitTree(const std::string& product,
|
||||
DominatorTree::Node* root) override {
|
||||
for (auto& child : root->children()) {
|
||||
visitNode(child.get(), 0);
|
||||
VisitNode(child.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string toString(DominatorTree* tree) {
|
||||
mBuffer.str("");
|
||||
mBuffer.clear();
|
||||
tree->accept(this);
|
||||
return mBuffer.str();
|
||||
std::string ToString(DominatorTree* tree) {
|
||||
buffer_.str("");
|
||||
buffer_.clear();
|
||||
tree->Accept(this);
|
||||
return buffer_.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)
|
||||
void VisitConfig(const DominatorTree::Node* node, const int indent) {
|
||||
auto config_string = node->value()->config.toString();
|
||||
buffer_ << std::string(indent, ' ')
|
||||
<< (config_string.isEmpty() ? "<default>" : config_string)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void visitNode(const DominatorTree::Node* node, const int indent) {
|
||||
visitConfig(node, indent);
|
||||
void VisitNode(const DominatorTree::Node* node, const int indent) {
|
||||
VisitConfig(node, indent);
|
||||
for (const auto& child : node->children()) {
|
||||
visitNode(child.get(), indent + mIndent);
|
||||
VisitNode(child.get(), indent + indent_);
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream mBuffer;
|
||||
const int mIndent = 2;
|
||||
std::stringstream buffer_;
|
||||
const int indent_ = 2;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DominatorTreeTest, DefaultDominatesEverything) {
|
||||
const ConfigDescription defaultConfig = {};
|
||||
const ConfigDescription landConfig = test::parseConfigOrDie("land");
|
||||
const ConfigDescription sw600dpLandConfig =
|
||||
test::parseConfigOrDie("sw600dp-land-v13");
|
||||
const ConfigDescription default_config = {};
|
||||
const ConfigDescription land_config = test::ParseConfigOrDie("land");
|
||||
const ConfigDescription sw600dp_land_config =
|
||||
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>(default_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
util::make_unique<ResourceConfigValue>(sw600dp_land_config, ""));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
@@ -84,22 +85,22 @@ TEST(DominatorTreeTest, DefaultDominatesEverything) {
|
||||
"<default>\n"
|
||||
" land\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
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 default_config = {};
|
||||
const ConfigDescription land_config = test::ParseConfigOrDie("land");
|
||||
const ConfigDescription sw600dp_land_config =
|
||||
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>(default_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(land_config, ""));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(defaultConfig, "phablet"));
|
||||
util::make_unique<ResourceConfigValue>(default_config, "phablet"));
|
||||
configs.push_back(
|
||||
util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet"));
|
||||
util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet"));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
@@ -109,34 +110,38 @@ TEST(DominatorTreeTest, ProductsAreDominatedSeparately) {
|
||||
" land\n"
|
||||
"<default>\n"
|
||||
" sw600dp-land-v13\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
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 default_config = {};
|
||||
const ConfigDescription en_config = test::ParseConfigOrDie("en");
|
||||
const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21");
|
||||
const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4");
|
||||
const ConfigDescription ldrtl_xhdpi_config =
|
||||
test::ParseConfigOrDie("ldrtl-xhdpi-v4");
|
||||
const ConfigDescription sw300dp_config =
|
||||
test::ParseConfigOrDie("sw300dp-v13");
|
||||
const ConfigDescription sw540dp_config =
|
||||
test::ParseConfigOrDie("sw540dp-v14");
|
||||
const ConfigDescription sw600dp_config =
|
||||
test::ParseConfigOrDie("sw600dp-v14");
|
||||
const ConfigDescription sw720dp_config =
|
||||
test::ParseConfigOrDie("sw720dp-v13");
|
||||
const ConfigDescription v20_config = 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>(default_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(en_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, ""));
|
||||
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, ""));
|
||||
util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(sw720dp_config, ""));
|
||||
configs.push_back(util::make_unique<ResourceConfigValue>(v20_config, ""));
|
||||
|
||||
DominatorTree tree(configs);
|
||||
PrettyPrinter printer;
|
||||
@@ -152,7 +157,7 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) {
|
||||
" sw600dp-v14\n"
|
||||
" sw720dp-v13\n"
|
||||
" v20\n";
|
||||
EXPECT_EQ(expected, printer.toString(&tree));
|
||||
EXPECT_EQ(expected, printer.ToString(&tree));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,97 +15,98 @@
|
||||
*/
|
||||
|
||||
#include "Flags.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Flags& Flags::requiredFlag(const StringPiece& name,
|
||||
Flags& Flags::RequiredFlag(const StringPiece& name,
|
||||
const StringPiece& description, std::string* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
*value = arg.ToString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, true, 1, false});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, true, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::requiredFlagList(const StringPiece& name,
|
||||
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());
|
||||
value->push_back(arg.ToString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, true, 1, false});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, true, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlag(const StringPiece& name,
|
||||
Flags& Flags::OptionalFlag(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
Maybe<std::string>* value) {
|
||||
auto func = [value](const StringPiece& arg) -> bool {
|
||||
*value = arg.toString();
|
||||
*value = arg.ToString();
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name,
|
||||
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());
|
||||
value->push_back(arg.ToString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalFlagList(const StringPiece& name,
|
||||
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());
|
||||
value->insert(arg.ToString());
|
||||
return true;
|
||||
};
|
||||
|
||||
mFlags.push_back(
|
||||
Flag{name.toString(), description.toString(), func, false, 1, false});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, false, 1, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Flags& Flags::optionalSwitch(const StringPiece& name,
|
||||
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});
|
||||
flags_.push_back(
|
||||
Flag{name.ToString(), description.ToString(), func, false, 0, false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
void Flags::Usage(const StringPiece& command, std::ostream* out) {
|
||||
constexpr size_t kWidth = 50;
|
||||
|
||||
*out << command << " [options]";
|
||||
for (const Flag& flag : mFlags) {
|
||||
for (const Flag& flag : flags_) {
|
||||
if (flag.required) {
|
||||
*out << " " << flag.name << " arg";
|
||||
}
|
||||
@@ -113,10 +114,10 @@ void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
|
||||
*out << " files...\n\nOptions:\n";
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
std::string argLine = flag.name;
|
||||
if (flag.numArgs > 0) {
|
||||
argLine += " arg";
|
||||
for (const Flag& flag : flags_) {
|
||||
std::string argline = flag.name;
|
||||
if (flag.num_args > 0) {
|
||||
argline += " arg";
|
||||
}
|
||||
|
||||
// Split the description by newlines and write out the argument (which is
|
||||
@@ -124,9 +125,9 @@ void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
// 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 = " ";
|
||||
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"
|
||||
@@ -134,29 +135,29 @@ void Flags::usage(const StringPiece& command, std::ostream* out) {
|
||||
out->flush();
|
||||
}
|
||||
|
||||
bool Flags::parse(const StringPiece& command,
|
||||
bool Flags::Parse(const StringPiece& command,
|
||||
const std::vector<StringPiece>& args,
|
||||
std::ostream* outError) {
|
||||
std::ostream* out_error) {
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
StringPiece arg = args[i];
|
||||
if (*(arg.data()) != '-') {
|
||||
mArgs.push_back(arg.toString());
|
||||
args_.push_back(arg.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
usage(command, outError);
|
||||
Usage(command, out_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (Flag& flag : mFlags) {
|
||||
for (Flag& flag : flags_) {
|
||||
if (arg == flag.name) {
|
||||
if (flag.numArgs > 0) {
|
||||
if (flag.num_args > 0) {
|
||||
i++;
|
||||
if (i >= args.size()) {
|
||||
*outError << flag.name << " missing argument.\n\n";
|
||||
usage(command, outError);
|
||||
*out_error << flag.name << " missing argument.\n\n";
|
||||
Usage(command, out_error);
|
||||
return false;
|
||||
}
|
||||
flag.action(args[i]);
|
||||
@@ -170,22 +171,22 @@ bool Flags::parse(const StringPiece& command,
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
*outError << "unknown option '" << arg << "'.\n\n";
|
||||
usage(command, outError);
|
||||
*out_error << "unknown option '" << arg << "'.\n\n";
|
||||
Usage(command, out_error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Flag& flag : mFlags) {
|
||||
for (const Flag& flag : flags_) {
|
||||
if (flag.required && !flag.parsed) {
|
||||
*outError << "missing required flag " << flag.name << "\n\n";
|
||||
usage(command, outError);
|
||||
*out_error << "missing required flag " << flag.name << "\n\n";
|
||||
Usage(command, out_error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Flags::getArgs() { return mArgs; }
|
||||
const std::vector<std::string>& Flags::GetArgs() { return args_; }
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,41 +17,41 @@
|
||||
#ifndef AAPT_FLAGS_H
|
||||
#define AAPT_FLAGS_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class Flags {
|
||||
public:
|
||||
Flags& requiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
Flags& RequiredFlag(const StringPiece& name, const StringPiece& description,
|
||||
std::string* value);
|
||||
Flags& requiredFlagList(const StringPiece& name,
|
||||
Flags& RequiredFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Flags& OptionalFlag(const StringPiece& name, const StringPiece& description,
|
||||
Maybe<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name,
|
||||
Flags& OptionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::vector<std::string>* value);
|
||||
Flags& optionalFlagList(const StringPiece& name,
|
||||
Flags& OptionalFlagList(const StringPiece& name,
|
||||
const StringPiece& description,
|
||||
std::unordered_set<std::string>* value);
|
||||
Flags& optionalSwitch(const StringPiece& name, const StringPiece& description,
|
||||
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,
|
||||
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 {
|
||||
@@ -59,13 +59,13 @@ class Flags {
|
||||
std::string description;
|
||||
std::function<bool(const StringPiece& value)> action;
|
||||
bool required;
|
||||
size_t numArgs;
|
||||
size_t num_args;
|
||||
|
||||
bool parsed;
|
||||
};
|
||||
|
||||
std::vector<Flag> mFlags;
|
||||
std::vector<std::string> mArgs;
|
||||
std::vector<Flag> flags_;
|
||||
std::vector<std::string> args_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,174 +15,176 @@
|
||||
*/
|
||||
|
||||
#include "Locale.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::ResTable_config;
|
||||
|
||||
void LocaleValue::setLanguage(const char* languageChars) {
|
||||
void LocaleValue::set_language(const char* language_chars) {
|
||||
size_t i = 0;
|
||||
while ((*languageChars) != '\0') {
|
||||
language[i++] = ::tolower(*languageChars);
|
||||
languageChars++;
|
||||
while ((*language_chars) != '\0') {
|
||||
language[i++] = ::tolower(*language_chars);
|
||||
language_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setRegion(const char* regionChars) {
|
||||
void LocaleValue::set_region(const char* region_chars) {
|
||||
size_t i = 0;
|
||||
while ((*regionChars) != '\0') {
|
||||
region[i++] = ::toupper(*regionChars);
|
||||
regionChars++;
|
||||
while ((*region_chars) != '\0') {
|
||||
region[i++] = ::toupper(*region_chars);
|
||||
region_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setScript(const char* scriptChars) {
|
||||
void LocaleValue::set_script(const char* script_chars) {
|
||||
size_t i = 0;
|
||||
while ((*scriptChars) != '\0') {
|
||||
while ((*script_chars) != '\0') {
|
||||
if (i == 0) {
|
||||
script[i++] = ::toupper(*scriptChars);
|
||||
script[i++] = ::toupper(*script_chars);
|
||||
} else {
|
||||
script[i++] = ::tolower(*scriptChars);
|
||||
script[i++] = ::tolower(*script_chars);
|
||||
}
|
||||
scriptChars++;
|
||||
script_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setVariant(const char* variantChars) {
|
||||
void LocaleValue::set_variant(const char* variant_chars) {
|
||||
size_t i = 0;
|
||||
while ((*variantChars) != '\0') {
|
||||
variant[i++] = *variantChars;
|
||||
variantChars++;
|
||||
while ((*variant_chars) != '\0') {
|
||||
variant[i++] = *variant_chars;
|
||||
variant_chars++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool isAlpha(const std::string& str) {
|
||||
static inline bool is_alpha(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
||||
}
|
||||
|
||||
static inline bool isNumber(const std::string& str) {
|
||||
static inline bool is_number(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
||||
}
|
||||
|
||||
bool LocaleValue::initFromFilterString(const StringPiece& str) {
|
||||
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, '_');
|
||||
std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
|
||||
|
||||
const int numTags = parts.size();
|
||||
const int num_tags = parts.size();
|
||||
bool valid = false;
|
||||
if (numTags >= 1) {
|
||||
if (num_tags >= 1) {
|
||||
const std::string& lang = parts[0];
|
||||
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
||||
setLanguage(lang.c_str());
|
||||
if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
||||
set_language(lang.c_str());
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid || numTags == 1) {
|
||||
if (!valid || num_tags == 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());
|
||||
if ((part2.length() == 2 && is_alpha(part2)) ||
|
||||
(part2.length() == 3 && is_number(part2))) {
|
||||
set_region(part2.c_str());
|
||||
} else if (part2.length() == 4 && is_alpha(part2)) {
|
||||
set_script(part2.c_str());
|
||||
} else if (part2.length() >= 4 && part2.length() <= 8) {
|
||||
setVariant(part2.c_str());
|
||||
set_variant(part2.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 2) {
|
||||
if (!valid || num_tags == 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))) &&
|
||||
if (((part3.length() == 2 && is_alpha(part3)) ||
|
||||
(part3.length() == 3 && is_number(part3))) &&
|
||||
script[0]) {
|
||||
setRegion(part3.c_str());
|
||||
set_region(part3.c_str());
|
||||
} else if (part3.length() >= 4 && part3.length() <= 8) {
|
||||
setVariant(part3.c_str());
|
||||
set_variant(part3.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 3) {
|
||||
if (!valid || num_tags == 3) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
const std::string& part4 = parts[3];
|
||||
if (part4.length() >= 4 && part4.length() <= 8) {
|
||||
setVariant(part4.c_str());
|
||||
set_variant(part4.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags > 4) {
|
||||
if (!valid || num_tags > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
||||
ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end) {
|
||||
const std::vector<std::string>::iterator startIter = iter;
|
||||
const std::vector<std::string>::iterator start_iter = 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, '+');
|
||||
std::vector<std::string> subtags = util::SplitAndLowercase(part, '+');
|
||||
subtags.erase(subtags.begin());
|
||||
if (subtags.size() == 1) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
set_language(subtags[0].c_str());
|
||||
} else if (subtags.size() == 2) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
set_language(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());
|
||||
set_region(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());
|
||||
set_script(subtags[1].c_str());
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
setVariant(subtags[1].c_str());
|
||||
set_variant(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());
|
||||
set_language(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());
|
||||
set_script(subtags[1].c_str());
|
||||
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
||||
setRegion(subtags[1].c_str());
|
||||
set_region(subtags[1].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -190,15 +192,15 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
||||
// 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());
|
||||
set_variant(subtags[2].c_str());
|
||||
} else {
|
||||
setRegion(subtags[2].c_str());
|
||||
set_region(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());
|
||||
set_language(subtags[0].c_str());
|
||||
set_script(subtags[1].c_str());
|
||||
set_region(subtags[2].c_str());
|
||||
set_variant(subtags[3].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -206,25 +208,25 @@ ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
||||
++iter;
|
||||
|
||||
} else {
|
||||
if ((part.length() == 2 || part.length() == 3) && isAlpha(part) &&
|
||||
if ((part.length() == 2 || part.length() == 3) && is_alpha(part) &&
|
||||
part != "car") {
|
||||
setLanguage(part.c_str());
|
||||
set_language(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);
|
||||
const std::string& region_part = *iter;
|
||||
if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
|
||||
set_region(region_part.c_str() + 1);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(iter - startIter);
|
||||
return static_cast<ssize_t>(iter - start_iter);
|
||||
}
|
||||
|
||||
void LocaleValue::initFromResTable(const ResTable_config& config) {
|
||||
void LocaleValue::InitFromResTable(const ResTable_config& config) {
|
||||
config.unpackLanguage(language);
|
||||
config.unpackRegion(region);
|
||||
if (config.localeScript[0] && !config.localeScriptWasComputed) {
|
||||
@@ -236,7 +238,7 @@ void LocaleValue::initFromResTable(const ResTable_config& config) {
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::writeTo(ResTable_config* out) const {
|
||||
void LocaleValue::WriteTo(ResTable_config* out) const {
|
||||
out->packLanguage(language);
|
||||
out->packRegion(region);
|
||||
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
#ifndef AAPT_LOCALE_VALUE_H
|
||||
#define AAPT_LOCALE_VALUE_H
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -39,23 +40,23 @@ struct LocaleValue {
|
||||
/**
|
||||
* Initialize this LocaleValue from a config string.
|
||||
*/
|
||||
bool initFromFilterString(const StringPiece& config);
|
||||
bool InitFromFilterString(const StringPiece& config);
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from parts of a vector.
|
||||
*/
|
||||
ssize_t initFromParts(std::vector<std::string>::iterator iter,
|
||||
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);
|
||||
void InitFromResTable(const android::ResTable_config& config);
|
||||
|
||||
/**
|
||||
* Set the locale in a ResTable_config from this LocaleValue.
|
||||
*/
|
||||
void writeTo(android::ResTable_config* out) const;
|
||||
void WriteTo(android::ResTable_config* out) const;
|
||||
|
||||
inline int compare(const LocaleValue& other) const;
|
||||
|
||||
@@ -67,10 +68,10 @@ struct LocaleValue {
|
||||
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);
|
||||
void set_language(const char* language);
|
||||
void set_region(const char* language);
|
||||
void set_script(const char* script);
|
||||
void set_variant(const char* variant);
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -15,18 +15,20 @@
|
||||
*/
|
||||
|
||||
#include "Locale.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestLanguage(const char* input,
|
||||
const char* lang) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input
|
||||
<< "'.";
|
||||
@@ -51,9 +53,9 @@ static ::testing::AssertionResult TestLanguage(const char* input,
|
||||
static ::testing::AssertionResult TestLanguageRegion(const char* input,
|
||||
const char* lang,
|
||||
const char* region) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(input, '-');
|
||||
std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input
|
||||
<< "'.";
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// DO NOT UPDATE, this is more of a marketing version.
|
||||
@@ -27,16 +27,16 @@ static const char* sMajorVersion = "2";
|
||||
// Update minor version whenever a feature or flag is added.
|
||||
static const char* sMinorVersion = "2";
|
||||
|
||||
int printVersion() {
|
||||
int PrintVersion() {
|
||||
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
|
||||
<< sMinorVersion << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern int compile(const std::vector<StringPiece>& args);
|
||||
extern int link(const std::vector<StringPiece>& args);
|
||||
extern int dump(const std::vector<StringPiece>& args);
|
||||
extern int diff(const std::vector<StringPiece>& args);
|
||||
extern int Compile(const std::vector<StringPiece>& args);
|
||||
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
|
||||
|
||||
@@ -52,15 +52,15 @@ int main(int argc, char** argv) {
|
||||
|
||||
aapt::StringPiece command(argv[0]);
|
||||
if (command == "compile" || command == "c") {
|
||||
return aapt::compile(args);
|
||||
return aapt::Compile(args);
|
||||
} else if (command == "link" || command == "l") {
|
||||
return aapt::link(args);
|
||||
return aapt::Link(args);
|
||||
} else if (command == "dump" || command == "d") {
|
||||
return aapt::dump(args);
|
||||
return aapt::Dump(args);
|
||||
} else if (command == "diff") {
|
||||
return aapt::diff(args);
|
||||
return aapt::Diff(args);
|
||||
} else if (command == "version") {
|
||||
return aapt::printVersion();
|
||||
return aapt::PrintVersion();
|
||||
}
|
||||
std::cerr << "unknown command '" << command << "'\n";
|
||||
} else {
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
#ifndef AAPT_NAME_MANGLER_H
|
||||
#define AAPT_NAME_MANGLER_H
|
||||
|
||||
#include "Resource.h"
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "Resource.h"
|
||||
#include "util/Maybe.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct NameManglerPolicy {
|
||||
@@ -31,37 +31,35 @@ struct NameManglerPolicy {
|
||||
* to this package are not mangled, and mangled references inherit this
|
||||
* package name.
|
||||
*/
|
||||
std::string targetPackageName;
|
||||
std::string target_package_name;
|
||||
|
||||
/**
|
||||
* We must know which references to mangle, and which to keep (android vs.
|
||||
* com.android.support).
|
||||
*/
|
||||
std::set<std::string> packagesToMangle;
|
||||
std::set<std::string> packages_to_mangle;
|
||||
};
|
||||
|
||||
class NameMangler {
|
||||
private:
|
||||
NameManglerPolicy mPolicy;
|
||||
|
||||
public:
|
||||
explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {}
|
||||
explicit NameMangler(NameManglerPolicy policy) : policy_(policy) {}
|
||||
|
||||
Maybe<ResourceName> mangleName(const ResourceName& name) {
|
||||
if (mPolicy.targetPackageName == name.package ||
|
||||
mPolicy.packagesToMangle.count(name.package) == 0) {
|
||||
Maybe<ResourceName> MangleName(const ResourceName& name) {
|
||||
if (policy_.target_package_name == name.package ||
|
||||
policy_.packages_to_mangle.count(name.package) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string mangledEntryName = mangleEntry(name.package, name.entry);
|
||||
return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName);
|
||||
std::string mangled_entry_name = MangleEntry(name.package, name.entry);
|
||||
return ResourceName(policy_.target_package_name, name.type,
|
||||
mangled_entry_name);
|
||||
}
|
||||
|
||||
bool shouldMangle(const std::string& package) const {
|
||||
if (package.empty() || mPolicy.targetPackageName == package) {
|
||||
bool ShouldMangle(const std::string& package) const {
|
||||
if (package.empty() || policy_.target_package_name == package) {
|
||||
return false;
|
||||
}
|
||||
return mPolicy.packagesToMangle.count(package) != 0;
|
||||
return policy_.packages_to_mangle.count(package) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +67,7 @@ class NameMangler {
|
||||
* 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,
|
||||
static std::string MangleEntry(const std::string& package,
|
||||
const std::string& name) {
|
||||
return package + "$" + name;
|
||||
}
|
||||
@@ -79,16 +77,20 @@ class NameMangler {
|
||||
* 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('$');
|
||||
static bool Unmangle(std::string* out_name, std::string* out_package) {
|
||||
size_t pivot = out_name->find('$');
|
||||
if (pivot == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outPackage->assign(outName->data(), pivot);
|
||||
outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
|
||||
out_package->assign(out_name->data(), pivot);
|
||||
out_name->assign(out_name->data() + pivot + 1,
|
||||
out_name->size() - (pivot + 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
NameManglerPolicy policy_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,31 +15,32 @@
|
||||
*/
|
||||
|
||||
#include "NameMangler.h"
|
||||
#include "test/Test.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(NameManglerTest, MangleName) {
|
||||
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 mangled_name = NameMangler::MangleEntry(package, name);
|
||||
EXPECT_EQ(mangled_name, "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 unmangled_package;
|
||||
std::string unmangled_name = mangled_name;
|
||||
ASSERT_TRUE(NameMangler::Unmangle(&unmangled_name, &unmangled_package));
|
||||
EXPECT_EQ(unmangled_name, "Platform.AppCompat");
|
||||
EXPECT_EQ(unmangled_package, "android.appcompat");
|
||||
}
|
||||
|
||||
TEST(NameManglerTest, IgnoreUnmangledName) {
|
||||
std::string package;
|
||||
std::string name = "foo_bar";
|
||||
|
||||
EXPECT_FALSE(NameMangler::unmangle(&name, &package));
|
||||
EXPECT_FALSE(NameMangler::Unmangle(&name, &package));
|
||||
EXPECT_EQ(name, "foo_bar");
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
|
||||
#include "Resource.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
StringPiece toString(ResourceType type) {
|
||||
StringPiece ToString(ResourceType type) {
|
||||
switch (type) {
|
||||
case ResourceType::kAnim:
|
||||
return "anim";
|
||||
@@ -100,7 +99,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{
|
||||
{"xml", ResourceType::kXml},
|
||||
};
|
||||
|
||||
const ResourceType* parseResourceType(const StringPiece& str) {
|
||||
const ResourceType* ParseResourceType(const StringPiece& str) {
|
||||
auto iter = sResourceTypeMap.find(str);
|
||||
if (iter == std::end(sResourceTypeMap)) {
|
||||
return nullptr;
|
||||
|
||||
@@ -17,12 +17,6 @@
|
||||
#ifndef AAPT_RESOURCE_H
|
||||
#define AAPT_RESOURCE_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Source.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <utils/JenkinsHash.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
@@ -30,6 +24,12 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/JenkinsHash.h"
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Source.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -62,13 +62,13 @@ enum class ResourceType {
|
||||
kXml,
|
||||
};
|
||||
|
||||
StringPiece toString(ResourceType type);
|
||||
StringPiece ToString(ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns a pointer to a valid ResourceType, or nullptr if
|
||||
* the string was invalid.
|
||||
*/
|
||||
const ResourceType* parseResourceType(const StringPiece& str);
|
||||
const ResourceType* ParseResourceType(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* A resource's name. This can uniquely identify
|
||||
@@ -76,16 +76,16 @@ const ResourceType* parseResourceType(const StringPiece& str);
|
||||
*/
|
||||
struct ResourceName {
|
||||
std::string package;
|
||||
ResourceType type;
|
||||
ResourceType type = ResourceType::kRaw;
|
||||
std::string entry;
|
||||
|
||||
ResourceName() : type(ResourceType::kRaw) {}
|
||||
ResourceName() = default;
|
||||
ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
|
||||
|
||||
int compare(const ResourceName& other) const;
|
||||
|
||||
bool isValid() const;
|
||||
std::string toString() const;
|
||||
bool is_valid() const;
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ struct ResourceName {
|
||||
*/
|
||||
struct ResourceNameRef {
|
||||
StringPiece package;
|
||||
ResourceType type;
|
||||
ResourceType type = ResourceType::kRaw;
|
||||
StringPiece entry;
|
||||
|
||||
ResourceNameRef() = default;
|
||||
@@ -108,8 +108,8 @@ struct ResourceNameRef {
|
||||
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
|
||||
ResourceNameRef& operator=(const ResourceName& rhs);
|
||||
|
||||
ResourceName toResourceName() const;
|
||||
bool isValid() const;
|
||||
ResourceName ToResourceName() const;
|
||||
bool is_valid() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,13 +128,13 @@ struct ResourceId {
|
||||
|
||||
ResourceId();
|
||||
ResourceId(const ResourceId& rhs);
|
||||
ResourceId(uint32_t resId); // NOLINT(implicit)
|
||||
ResourceId(uint32_t res_id); // 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 is_valid() const;
|
||||
uint8_t package_id() const;
|
||||
uint8_t type_id() const;
|
||||
uint16_t entry_id() const;
|
||||
};
|
||||
|
||||
struct SourcedResourceName {
|
||||
@@ -153,7 +153,7 @@ struct ResourceFile {
|
||||
Source source;
|
||||
|
||||
// Exported symbols
|
||||
std::vector<SourcedResourceName> exportedSymbols;
|
||||
std::vector<SourcedResourceName> exported_symbols;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -196,24 +196,24 @@ inline ResourceId::ResourceId() : id(0) {}
|
||||
|
||||
inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {}
|
||||
|
||||
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {}
|
||||
inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {}
|
||||
|
||||
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
|
||||
: id((p << 24) | (t << 16) | e) {}
|
||||
|
||||
inline bool ResourceId::isValid() const {
|
||||
inline bool ResourceId::is_valid() const {
|
||||
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::packageId() const {
|
||||
inline uint8_t ResourceId::package_id() const {
|
||||
return static_cast<uint8_t>(id >> 24);
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::typeId() const {
|
||||
inline uint8_t ResourceId::type_id() const {
|
||||
return static_cast<uint8_t>(id >> 16);
|
||||
}
|
||||
|
||||
inline uint16_t ResourceId::entryId() const {
|
||||
inline uint16_t ResourceId::entry_id() const {
|
||||
return static_cast<uint16_t>(id);
|
||||
}
|
||||
|
||||
@@ -234,13 +234,13 @@ inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceId& resId) {
|
||||
std::ios_base::fmtflags oldFlags = out.flags();
|
||||
char oldFill = out.fill();
|
||||
const ResourceId& res_id) {
|
||||
std::ios_base::fmtflags old_flags = out.flags();
|
||||
char old_fill = out.fill();
|
||||
out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex
|
||||
<< resId.id;
|
||||
out.flags(oldFlags);
|
||||
out.fill(oldFill);
|
||||
<< res_id.id;
|
||||
out.flags(old_flags);
|
||||
out.fill(old_fill);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceType& val) {
|
||||
return out << toString(val);
|
||||
return out << ToString(val);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -259,7 +259,7 @@ inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
|
||||
inline ResourceName::ResourceName(const StringPiece& p, ResourceType t,
|
||||
const StringPiece& e)
|
||||
: package(p.toString()), type(t), entry(e.toString()) {}
|
||||
: package(p.ToString()), type(t), entry(e.ToString()) {}
|
||||
|
||||
inline int ResourceName::compare(const ResourceName& other) const {
|
||||
int cmp = package.compare(other.package);
|
||||
@@ -270,7 +270,7 @@ inline int ResourceName::compare(const ResourceName& other) const {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
inline bool ResourceName::isValid() const {
|
||||
inline bool ResourceName::is_valid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
return out << name.type << "/" << name.entry;
|
||||
}
|
||||
|
||||
inline std::string ResourceName::toString() const {
|
||||
inline std::string ResourceName::ToString() const {
|
||||
std::stringstream stream;
|
||||
stream << *this;
|
||||
return stream.str();
|
||||
@@ -321,11 +321,11 @@ inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ResourceName ResourceNameRef::toResourceName() const {
|
||||
inline ResourceName ResourceNameRef::ToResourceName() const {
|
||||
return ResourceName(package, type, entry);
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::isValid() const {
|
||||
inline bool ResourceNameRef::is_valid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,10 @@
|
||||
#ifndef AAPT_RESOURCE_PARSER_H
|
||||
#define AAPT_RESOURCE_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceTable.h"
|
||||
@@ -26,8 +30,6 @@
|
||||
#include "util/StringPiece.h"
|
||||
#include "xml/XmlPullParser.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ParsedResource;
|
||||
@@ -42,7 +44,7 @@ struct ResourceParserOptions {
|
||||
* Whether positional arguments in formatted strings are treated as errors or
|
||||
* warnings.
|
||||
*/
|
||||
bool errorOnPositionalArguments = true;
|
||||
bool error_on_positional_arguments = true;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -53,70 +55,71 @@ class ResourceParser {
|
||||
ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
|
||||
const ConfigDescription& config,
|
||||
const ResourceParserOptions& options = {});
|
||||
|
||||
ResourceParser(const ResourceParser&) = delete; // No copy.
|
||||
|
||||
bool parse(xml::XmlPullParser* parser);
|
||||
bool Parse(xml::XmlPullParser* parser);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
|
||||
|
||||
/*
|
||||
* 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`
|
||||
* with formatting). If successful, `out_style_string`
|
||||
* contains the escaped and whitespace trimmed text, while `out_raw_string`
|
||||
* contains the unescaped text. Returns true on success.
|
||||
*/
|
||||
bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString,
|
||||
StyleString* outStyleString);
|
||||
bool FlattenXmlSubtree(xml::XmlPullParser* parser,
|
||||
std::string* out_raw_string,
|
||||
StyleString* out_style_string);
|
||||
|
||||
/*
|
||||
* 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
|
||||
* The type of Item that can be parsed is denoted by the `type_mask`.
|
||||
* If `allow_raw_value` 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);
|
||||
std::unique_ptr<Item> ParseXml(xml::XmlPullParser* parser,
|
||||
const uint32_t type_mask,
|
||||
const bool allow_raw_value);
|
||||
|
||||
bool parseResources(xml::XmlPullParser* parser);
|
||||
bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool ParseResources(xml::XmlPullParser* parser);
|
||||
bool ParseResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
||||
bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource,
|
||||
bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource,
|
||||
uint32_t format);
|
||||
bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
||||
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 ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParsePublicGroup(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseSymbolImpl(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseAddResource(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
|
||||
bool weak);
|
||||
Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
|
||||
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,
|
||||
bool ParseStyle(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
|
||||
bool ParseDeclareStyleable(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseIntegerArray(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseStringArray(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
|
||||
uint32_t typeMask);
|
||||
bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTable* mTable;
|
||||
Source mSource;
|
||||
ConfigDescription mConfig;
|
||||
ResourceParserOptions mOptions;
|
||||
IDiagnostics* diag_;
|
||||
ResourceTable* table_;
|
||||
Source source_;
|
||||
ConfigDescription config_;
|
||||
ResourceParserOptions options_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,79 +15,82 @@
|
||||
*/
|
||||
|
||||
#include "ResourceParser.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "test/Test.h"
|
||||
#include "xml/XmlPullParser.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr const char* kXmlPreamble =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
|
||||
TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<attr name=\"foo\"/>" << std::endl;
|
||||
ResourceTable table;
|
||||
ResourceParser parser(context->getDiagnostics(), &table, Source{"test"}, {});
|
||||
xml::XmlPullParser xmlParser(input);
|
||||
ASSERT_FALSE(parser.parse(&xmlParser));
|
||||
ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {});
|
||||
xml::XmlPullParser xml_parser(input);
|
||||
ASSERT_FALSE(parser.Parse(&xml_parser));
|
||||
}
|
||||
|
||||
struct ResourceParserTest : public ::testing::Test {
|
||||
ResourceTable mTable;
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
class ResourceParserTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override { context_ = test::ContextBuilder().Build(); }
|
||||
|
||||
void SetUp() override { mContext = test::ContextBuilder().build(); }
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str) {
|
||||
return testParse(str, ConfigDescription{});
|
||||
::testing::AssertionResult TestParse(const StringPiece& str) {
|
||||
return TestParse(str, ConfigDescription{});
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str,
|
||||
::testing::AssertionResult TestParse(const StringPiece& str,
|
||||
const ConfigDescription& config) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<resources>\n" << str << "\n</resources>" << std::endl;
|
||||
ResourceParserOptions parserOptions;
|
||||
ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{"test"},
|
||||
ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
|
||||
config, parserOptions);
|
||||
xml::XmlPullParser xmlParser(input);
|
||||
if (parser.parse(&xmlParser)) {
|
||||
if (parser.Parse(&xmlParser)) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
protected:
|
||||
ResourceTable table_;
|
||||
std::unique_ptr<IAaptContext> context_;
|
||||
};
|
||||
|
||||
TEST_F(ResourceParserTest, ParseQuotedString) {
|
||||
std::string input = "<string name=\"foo\"> \" hey there \" </string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* str = test::getValue<String>(&mTable, "string/foo");
|
||||
String* str = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::string(" hey there "), *str->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseEscapedString) {
|
||||
std::string input = "<string name=\"foo\">\\?123</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* str = test::getValue<String>(&mTable, "string/foo");
|
||||
String* str = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::string("?123"), *str->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseFormattedString) {
|
||||
std::string input = "<string name=\"foo\">%d %s</string>";
|
||||
ASSERT_FALSE(testParse(input));
|
||||
ASSERT_FALSE(TestParse(input));
|
||||
|
||||
input = "<string name=\"foo\">%1$d %2$s</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyledString) {
|
||||
@@ -96,32 +99,32 @@ TEST_F(ResourceParserTest, ParseStyledString) {
|
||||
// use UTF-16 length and not UTF-18 length.
|
||||
std::string input =
|
||||
"<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
StyledString* str = test::getValue<StyledString>(&mTable, "string/foo");
|
||||
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
|
||||
const std::string expectedStr = "This is my aunt\u2019s string";
|
||||
EXPECT_EQ(expectedStr, *str->value->str);
|
||||
const std::string expected_str = "This is my aunt\u2019s string";
|
||||
EXPECT_EQ(expected_str, *str->value->str);
|
||||
EXPECT_EQ(1u, str->value->spans.size());
|
||||
|
||||
EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
|
||||
EXPECT_EQ(17u, str->value->spans[0].firstChar);
|
||||
EXPECT_EQ(23u, str->value->spans[0].lastChar);
|
||||
EXPECT_EQ(17u, str->value->spans[0].first_char);
|
||||
EXPECT_EQ(23u, str->value->spans[0].last_char);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStringWithWhitespace) {
|
||||
std::string input = "<string name=\"foo\"> This is what I think </string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* str = test::getValue<String>(&mTable, "string/foo");
|
||||
String* str = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::string("This is what I think"), *str->value);
|
||||
|
||||
input = "<string name=\"foo2\">\" This is what I think \"</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
str = test::getValue<String>(&mTable, "string/foo2");
|
||||
str = test::GetValue<String>(&table_, "string/foo2");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::string(" This is what I think "), *str->value);
|
||||
}
|
||||
@@ -131,16 +134,16 @@ TEST_F(ResourceParserTest, IgnoreXliffTags) {
|
||||
"<string name=\"foo\" \n"
|
||||
" xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
|
||||
" There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* str = test::getValue<String>(&mTable, "string/foo");
|
||||
String* str = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseNull) {
|
||||
std::string input = "<integer name=\"foo\">@null</integer>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
// The Android runtime treats a value of android::Res_value::TYPE_NULL as
|
||||
// a non-existing value, and this causes problems in styles when trying to
|
||||
@@ -149,7 +152,7 @@ TEST_F(ResourceParserTest, ParseNull) {
|
||||
// android::Res_value::TYPE_REFERENCE
|
||||
// with a data value of 0.
|
||||
BinaryPrimitive* integer =
|
||||
test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
|
||||
test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
|
||||
ASSERT_NE(nullptr, integer);
|
||||
EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE),
|
||||
integer->value.dataType);
|
||||
@@ -158,10 +161,10 @@ TEST_F(ResourceParserTest, ParseNull) {
|
||||
|
||||
TEST_F(ResourceParserTest, ParseEmpty) {
|
||||
std::string input = "<integer name=\"foo\">@empty</integer>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
BinaryPrimitive* integer =
|
||||
test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
|
||||
test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
|
||||
ASSERT_NE(nullptr, integer);
|
||||
EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
|
||||
EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
|
||||
@@ -171,15 +174,15 @@ TEST_F(ResourceParserTest, ParseAttr) {
|
||||
std::string input =
|
||||
"<attr name=\"foo\" format=\"string\"/>\n"
|
||||
"<attr name=\"bar\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
|
||||
|
||||
attr = test::getValue<Attribute>(&mTable, "attr/bar");
|
||||
attr = test::GetValue<Attribute>(&table_, "attr/bar");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask);
|
||||
}
|
||||
|
||||
// Old AAPT allowed attributes to be defined under different configurations, but
|
||||
@@ -188,42 +191,42 @@ TEST_F(ResourceParserTest, ParseAttr) {
|
||||
// behavior.
|
||||
TEST_F(ResourceParserTest,
|
||||
ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
|
||||
const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
|
||||
const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
|
||||
std::string input = R"EOF(
|
||||
<attr name="foo" />
|
||||
<declare-styleable name="bar">
|
||||
<attr name="baz" />
|
||||
</declare-styleable>)EOF";
|
||||
ASSERT_TRUE(testParse(input, watchConfig));
|
||||
ASSERT_TRUE(TestParse(input, watch_config));
|
||||
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo",
|
||||
watchConfig));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz",
|
||||
watchConfig));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(
|
||||
&mTable, "styleable/bar", watchConfig));
|
||||
EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo",
|
||||
watch_config));
|
||||
EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz",
|
||||
watch_config));
|
||||
EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(
|
||||
&table_, "styleable/bar", watch_config));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo"));
|
||||
EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz"));
|
||||
EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar"));
|
||||
EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo"));
|
||||
EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz"));
|
||||
EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
|
||||
std::string input =
|
||||
"<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
|
||||
EXPECT_EQ(10, attr->minInt);
|
||||
EXPECT_EQ(23, attr->maxInt);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask);
|
||||
EXPECT_EQ(10, attr->min_int);
|
||||
EXPECT_EQ(23, attr->max_int);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
|
||||
std::string input =
|
||||
"<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
|
||||
ASSERT_FALSE(testParse(input));
|
||||
ASSERT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
|
||||
@@ -232,11 +235,11 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
|
||||
" <attr name=\"foo\" />\n"
|
||||
"</declare-styleable>\n"
|
||||
"<attr name=\"foo\" format=\"string\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
|
||||
@@ -247,11 +250,11 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
|
||||
"<declare-styleable name=\"Window\">\n"
|
||||
" <attr name=\"foo\" format=\"boolean\"/>\n"
|
||||
"</declare-styleable>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseEnumAttr) {
|
||||
@@ -261,24 +264,24 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
|
||||
" <enum name=\"bat\" value=\"1\"/>\n"
|
||||
" <enum name=\"baz\" value=\"2\"/>\n"
|
||||
"</attr>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
ASSERT_NE(enumAttr, nullptr);
|
||||
EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
|
||||
ASSERT_EQ(enumAttr->symbols.size(), 3u);
|
||||
Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(enum_attr, nullptr);
|
||||
EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM);
|
||||
ASSERT_EQ(enum_attr->symbols.size(), 3u);
|
||||
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar");
|
||||
EXPECT_EQ(enumAttr->symbols[0].value, 0u);
|
||||
AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar");
|
||||
EXPECT_EQ(enum_attr->symbols[0].value, 0u);
|
||||
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat");
|
||||
EXPECT_EQ(enumAttr->symbols[1].value, 1u);
|
||||
AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat");
|
||||
EXPECT_EQ(enum_attr->symbols[1].value, 1u);
|
||||
|
||||
AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz");
|
||||
EXPECT_EQ(enumAttr->symbols[2].value, 2u);
|
||||
AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz");
|
||||
EXPECT_EQ(enum_attr->symbols[2].value, 2u);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseFlagAttr) {
|
||||
@@ -288,29 +291,29 @@ TEST_F(ResourceParserTest, ParseFlagAttr) {
|
||||
" <flag name=\"bat\" value=\"1\"/>\n"
|
||||
" <flag name=\"baz\" value=\"2\"/>\n"
|
||||
"</attr>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
ASSERT_NE(nullptr, flagAttr);
|
||||
EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
|
||||
ASSERT_EQ(flagAttr->symbols.size(), 3u);
|
||||
Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, flag_attr);
|
||||
EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS);
|
||||
ASSERT_EQ(flag_attr->symbols.size(), 3u);
|
||||
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar");
|
||||
EXPECT_EQ(flagAttr->symbols[0].value, 0u);
|
||||
AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name);
|
||||
EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar");
|
||||
EXPECT_EQ(flag_attr->symbols[0].value, 0u);
|
||||
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat");
|
||||
EXPECT_EQ(flagAttr->symbols[1].value, 1u);
|
||||
AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name);
|
||||
EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat");
|
||||
EXPECT_EQ(flag_attr->symbols[1].value, 1u);
|
||||
|
||||
AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz");
|
||||
EXPECT_EQ(flagAttr->symbols[2].value, 2u);
|
||||
AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name);
|
||||
EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz");
|
||||
EXPECT_EQ(flag_attr->symbols[2].value, 2u);
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> flagValue =
|
||||
ResourceUtils::tryParseFlagSymbol(flagAttr, "baz|bat");
|
||||
ASSERT_NE(nullptr, flagValue);
|
||||
EXPECT_EQ(flagValue->value.data, 1u | 2u);
|
||||
std::unique_ptr<BinaryPrimitive> flag_value =
|
||||
ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat");
|
||||
ASSERT_NE(nullptr, flag_value);
|
||||
EXPECT_EQ(flag_value->value.data, 1u | 2u);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
|
||||
@@ -320,7 +323,7 @@ TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
|
||||
" <enum name=\"bat\" value=\"1\"/>\n"
|
||||
" <enum name=\"bat\" value=\"2\"/>\n"
|
||||
"</attr>";
|
||||
ASSERT_FALSE(testParse(input));
|
||||
ASSERT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyle) {
|
||||
@@ -330,38 +333,38 @@ TEST_F(ResourceParserTest, ParseStyle) {
|
||||
" <item name=\"bat\">@string/hey</item>\n"
|
||||
" <item name=\"baz\"><b>hey</b></item>\n"
|
||||
"</style>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie("style/fu"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("style/fu"),
|
||||
style->parent.value().name.value());
|
||||
ASSERT_EQ(3u, style->entries.size());
|
||||
|
||||
AAPT_ASSERT_TRUE(style->entries[0].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie("attr/bar"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("attr/bar"),
|
||||
style->entries[0].key.name.value());
|
||||
|
||||
AAPT_ASSERT_TRUE(style->entries[1].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie("attr/bat"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("attr/bat"),
|
||||
style->entries[1].key.name.value());
|
||||
|
||||
AAPT_ASSERT_TRUE(style->entries[2].key.name);
|
||||
EXPECT_EQ(test::parseNameOrDie("attr/baz"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("attr/baz"),
|
||||
style->entries[2].key.name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
|
||||
std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"),
|
||||
style->parent.value().name.value());
|
||||
}
|
||||
|
||||
@@ -369,13 +372,13 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
|
||||
std::string input =
|
||||
"<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
" name=\"foo\" parent=\"app:Theme\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(test::parseNameOrDie("android:style/Theme"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"),
|
||||
style->parent.value().name.value());
|
||||
}
|
||||
|
||||
@@ -385,55 +388,55 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
|
||||
"name=\"foo\">\n"
|
||||
" <item name=\"app:bar\">0</item>\n"
|
||||
"</style>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_NE(nullptr, style);
|
||||
ASSERT_EQ(1u, style->entries.size());
|
||||
EXPECT_EQ(test::parseNameOrDie("android:attr/bar"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"),
|
||||
style->entries[0].key.name.value());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
|
||||
std::string input = "<style name=\"foo.bar\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo.bar");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
AAPT_ASSERT_TRUE(style->parent.value().name);
|
||||
EXPECT_EQ(style->parent.value().name.value(),
|
||||
test::parseNameOrDie("style/foo"));
|
||||
EXPECT_TRUE(style->parentInferred);
|
||||
test::ParseNameOrDie("style/foo"));
|
||||
EXPECT_TRUE(style->parent_inferred);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest,
|
||||
ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
|
||||
std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo.bar");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_EXPECT_FALSE(style->parent);
|
||||
EXPECT_FALSE(style->parentInferred);
|
||||
EXPECT_FALSE(style->parent_inferred);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
|
||||
std::string input =
|
||||
R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Style* style = test::getValue<Style>(&mTable, "style/foo");
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_NE(nullptr, style);
|
||||
AAPT_ASSERT_TRUE(style->parent);
|
||||
EXPECT_TRUE(style->parent.value().privateReference);
|
||||
EXPECT_TRUE(style->parent.value().private_reference);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
|
||||
std::string input = "<string name=\"foo\">@+id/bar</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Id* id = test::getValue<Id>(&mTable, "id/bar");
|
||||
Id* id = test::GetValue<Id>(&table_, "id/bar");
|
||||
ASSERT_NE(id, nullptr);
|
||||
}
|
||||
|
||||
@@ -446,35 +449,35 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
|
||||
" <enum name=\"foo\" value=\"1\"/>\n"
|
||||
" </attr>\n"
|
||||
"</declare-styleable>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
mTable.findResource(test::parseNameOrDie("styleable/foo"));
|
||||
table_.FindResource(test::ParseNameOrDie("styleable/foo"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
|
||||
EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
EXPECT_TRUE(attr->IsWeak());
|
||||
|
||||
attr = test::getValue<Attribute>(&mTable, "attr/bat");
|
||||
attr = test::GetValue<Attribute>(&table_, "attr/bat");
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
EXPECT_TRUE(attr->IsWeak());
|
||||
|
||||
attr = test::getValue<Attribute>(&mTable, "attr/baz");
|
||||
attr = test::GetValue<Attribute>(&table_, "attr/baz");
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
EXPECT_TRUE(attr->IsWeak());
|
||||
EXPECT_EQ(1u, attr->symbols.size());
|
||||
|
||||
EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo"));
|
||||
EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo"));
|
||||
|
||||
Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
|
||||
Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
|
||||
ASSERT_NE(styleable, nullptr);
|
||||
ASSERT_EQ(3u, styleable->entries.size());
|
||||
|
||||
EXPECT_EQ(test::parseNameOrDie("attr/bar"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("attr/bar"),
|
||||
styleable->entries[0].name.value());
|
||||
EXPECT_EQ(test::parseNameOrDie("attr/bat"),
|
||||
EXPECT_EQ(test::ParseNameOrDie("attr/bat"),
|
||||
styleable->entries[1].name.value());
|
||||
}
|
||||
|
||||
@@ -485,16 +488,16 @@ TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
|
||||
" <attr name=\"*android:bar\" />\n"
|
||||
" <attr name=\"privAndroid:bat\" />\n"
|
||||
"</declare-styleable>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
|
||||
ASSERT_NE(nullptr, styleable);
|
||||
ASSERT_EQ(2u, styleable->entries.size());
|
||||
|
||||
EXPECT_TRUE(styleable->entries[0].privateReference);
|
||||
EXPECT_TRUE(styleable->entries[0].private_reference);
|
||||
AAPT_ASSERT_TRUE(styleable->entries[0].name);
|
||||
EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
|
||||
|
||||
EXPECT_TRUE(styleable->entries[1].privateReference);
|
||||
EXPECT_TRUE(styleable->entries[1].private_reference);
|
||||
AAPT_ASSERT_TRUE(styleable->entries[1].name);
|
||||
EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
|
||||
}
|
||||
@@ -506,15 +509,15 @@ TEST_F(ResourceParserTest, ParseArray) {
|
||||
" <item>hey</item>\n"
|
||||
" <item>23</item>\n"
|
||||
"</array>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Array* array = test::getValue<Array>(&mTable, "array/foo");
|
||||
Array* array = test::GetValue<Array>(&table_, "array/foo");
|
||||
ASSERT_NE(array, nullptr);
|
||||
ASSERT_EQ(3u, array->items.size());
|
||||
|
||||
EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
|
||||
EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
|
||||
EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
|
||||
EXPECT_NE(nullptr, ValueCast<Reference>(array->items[0].get()));
|
||||
EXPECT_NE(nullptr, ValueCast<String>(array->items[1].get()));
|
||||
EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(array->items[2].get()));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStringArray) {
|
||||
@@ -522,8 +525,8 @@ TEST_F(ResourceParserTest, ParseStringArray) {
|
||||
"<string-array name=\"foo\">\n"
|
||||
" <item>\"Werk\"</item>\n"
|
||||
"</string-array>\n";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo"));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParsePlural) {
|
||||
@@ -532,18 +535,18 @@ TEST_F(ResourceParserTest, ParsePlural) {
|
||||
" <item quantity=\"other\">apples</item>\n"
|
||||
" <item quantity=\"one\">apple</item>\n"
|
||||
"</plurals>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
|
||||
std::string input =
|
||||
"<!--This is a comment-->\n"
|
||||
"<string name=\"foo\">Hi</string>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* value = test::getValue<String>(&mTable, "string/foo");
|
||||
String* value = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(value->getComment(), "This is a comment");
|
||||
EXPECT_EQ(value->GetComment(), "This is a comment");
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
|
||||
@@ -552,11 +555,11 @@ TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
|
||||
"<!--Two-->\n"
|
||||
"<string name=\"foo\">Hi</string>";
|
||||
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* value = test::getValue<String>(&mTable, "string/foo");
|
||||
String* value = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(value->getComment(), "Two");
|
||||
EXPECT_EQ(value->GetComment(), "Two");
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
|
||||
@@ -567,11 +570,11 @@ TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
|
||||
"<!--Two-->\n"
|
||||
"</string>";
|
||||
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
String* value = test::getValue<String>(&mTable, "string/foo");
|
||||
String* value = test::GetValue<String>(&table_, "string/foo");
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(value->getComment(), "One");
|
||||
EXPECT_EQ(value->GetComment(), "One");
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseNestedComments) {
|
||||
@@ -588,21 +591,21 @@ TEST_F(ResourceParserTest, ParseNestedComments) {
|
||||
<!-- The very first -->
|
||||
<enum name="one" value="1" />
|
||||
</attr>)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
|
||||
Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo");
|
||||
ASSERT_NE(nullptr, styleable);
|
||||
ASSERT_EQ(1u, styleable->entries.size());
|
||||
|
||||
EXPECT_EQ(StringPiece("The name of the bar"),
|
||||
styleable->entries.front().getComment());
|
||||
styleable->entries.front().GetComment());
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
ASSERT_EQ(1u, attr->symbols.size());
|
||||
|
||||
EXPECT_EQ(StringPiece("The very first"),
|
||||
attr->symbols.front().symbol.getComment());
|
||||
attr->symbols.front().symbol.GetComment());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -611,9 +614,9 @@ TEST_F(ResourceParserTest, ParseNestedComments) {
|
||||
*/
|
||||
TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
|
||||
std::string input = "<public type=\"id\" name=\"foo\"/>";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Id* id = test::getValue<Id>(&mTable, "id/foo");
|
||||
Id* id = test::GetValue<Id>(&table_, "id/foo");
|
||||
ASSERT_NE(nullptr, id);
|
||||
}
|
||||
|
||||
@@ -626,26 +629,26 @@ TEST_F(ResourceParserTest, KeepAllProducts) {
|
||||
<string name="bit" product="phablet">hoot</string>
|
||||
<string name="bot" product="default">yes</string>
|
||||
)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/foo",
|
||||
ConfigDescription::defaultConfig(), "phone"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/foo",
|
||||
ConfigDescription::defaultConfig(), "no-sdcard"));
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/foo",
|
||||
ConfigDescription::DefaultConfig(), "phone"));
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/foo",
|
||||
ConfigDescription::DefaultConfig(), "no-sdcard"));
|
||||
EXPECT_NE(nullptr,
|
||||
test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/bar", ConfigDescription::defaultConfig(), ""));
|
||||
test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/bar", ConfigDescription::DefaultConfig(), ""));
|
||||
EXPECT_NE(nullptr,
|
||||
test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/baz", ConfigDescription::defaultConfig(), ""));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/bit",
|
||||
ConfigDescription::defaultConfig(), "phablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(
|
||||
&mTable, "string/bot",
|
||||
ConfigDescription::defaultConfig(), "default"));
|
||||
test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/baz", ConfigDescription::DefaultConfig(), ""));
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/bit",
|
||||
ConfigDescription::DefaultConfig(), "phablet"));
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>(
|
||||
&table_, "string/bot",
|
||||
ConfigDescription::DefaultConfig(), "default"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
|
||||
@@ -654,61 +657,61 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
|
||||
<public name="foo" />
|
||||
<public name="bar" />
|
||||
</public-group>)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
mTable.findResource(test::parseNameOrDie("attr/foo"));
|
||||
table_.FindResource(test::ParseNameOrDie("attr/foo"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
|
||||
AAPT_ASSERT_TRUE(result.value().package->id);
|
||||
AAPT_ASSERT_TRUE(result.value().type->id);
|
||||
AAPT_ASSERT_TRUE(result.value().entry->id);
|
||||
ResourceId actualId(result.value().package->id.value(),
|
||||
result.value().type->id.value(),
|
||||
result.value().entry->id.value());
|
||||
EXPECT_EQ(ResourceId(0x01010040), actualId);
|
||||
ResourceId actual_id(result.value().package->id.value(),
|
||||
result.value().type->id.value(),
|
||||
result.value().entry->id.value());
|
||||
EXPECT_EQ(ResourceId(0x01010040), actual_id);
|
||||
|
||||
result = mTable.findResource(test::parseNameOrDie("attr/bar"));
|
||||
result = table_.FindResource(test::ParseNameOrDie("attr/bar"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
|
||||
AAPT_ASSERT_TRUE(result.value().package->id);
|
||||
AAPT_ASSERT_TRUE(result.value().type->id);
|
||||
AAPT_ASSERT_TRUE(result.value().entry->id);
|
||||
actualId = ResourceId(result.value().package->id.value(),
|
||||
result.value().type->id.value(),
|
||||
result.value().entry->id.value());
|
||||
EXPECT_EQ(ResourceId(0x01010041), actualId);
|
||||
actual_id = ResourceId(result.value().package->id.value(),
|
||||
result.value().type->id.value(),
|
||||
result.value().entry->id.value());
|
||||
EXPECT_EQ(ResourceId(0x01010041), actual_id);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
|
||||
std::string input =
|
||||
R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
|
||||
ASSERT_FALSE(testParse(input));
|
||||
ASSERT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest,
|
||||
AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
|
||||
std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
mTable.findResource(test::parseNameOrDie("string/bar"));
|
||||
table_.FindResource(test::ParseNameOrDie("string/bar"));
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
const ResourceEntry* entry = result.value().entry;
|
||||
ASSERT_NE(nullptr, entry);
|
||||
EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state);
|
||||
EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.state);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
|
||||
std::string input =
|
||||
R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
|
||||
ASSERT_TRUE(testParse(input));
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
BinaryPrimitive* val =
|
||||
test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
|
||||
test::GetValue<BinaryPrimitive>(&table_, "integer/foo");
|
||||
ASSERT_NE(nullptr, val);
|
||||
|
||||
EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
@@ -29,28 +30,29 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs,
|
||||
ResourceType rhs) {
|
||||
static bool less_than_type(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) {
|
||||
static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs,
|
||||
const StringPiece& rhs) {
|
||||
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findPackage(const StringPiece& name) {
|
||||
ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
auto iter =
|
||||
std::lower_bound(packages.begin(), last, name,
|
||||
less_than_struct_with_name<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
|
||||
ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
|
||||
for (auto& package : packages) {
|
||||
if (package->id && package->id.value() == id) {
|
||||
return package.get();
|
||||
@@ -59,9 +61,9 @@ ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name,
|
||||
ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name,
|
||||
Maybe<uint8_t> id) {
|
||||
ResourceTablePackage* package = findOrCreatePackage(name);
|
||||
ResourceTablePackage* package = FindOrCreatePackage(name);
|
||||
if (id && !package->id) {
|
||||
package->id = id;
|
||||
return package;
|
||||
@@ -73,61 +75,62 @@ ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name,
|
||||
return package;
|
||||
}
|
||||
|
||||
ResourceTablePackage* ResourceTable::findOrCreatePackage(
|
||||
ResourceTablePackage* ResourceTable::FindOrCreatePackage(
|
||||
const StringPiece& name) {
|
||||
const auto last = packages.end();
|
||||
auto iter = std::lower_bound(packages.begin(), last, name,
|
||||
lessThanStructWithName<ResourceTablePackage>);
|
||||
auto iter =
|
||||
std::lower_bound(packages.begin(), last, name,
|
||||
less_than_struct_with_name<ResourceTablePackage>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTablePackage> newPackage =
|
||||
std::unique_ptr<ResourceTablePackage> new_package =
|
||||
util::make_unique<ResourceTablePackage>();
|
||||
newPackage->name = name.toString();
|
||||
return packages.emplace(iter, std::move(newPackage))->get();
|
||||
new_package->name = name.ToString();
|
||||
return packages.emplace(iter, std::move(new_package))->get();
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findType(ResourceType type) {
|
||||
ResourceTableType* ResourceTablePackage::FindType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) {
|
||||
ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) {
|
||||
const auto last = types.end();
|
||||
auto iter = std::lower_bound(types.begin(), last, type, lessThanType);
|
||||
auto iter = std::lower_bound(types.begin(), last, type, less_than_type);
|
||||
if (iter != last && (*iter)->type == type) {
|
||||
return iter->get();
|
||||
}
|
||||
return types.emplace(iter, new ResourceTableType(type))->get();
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findEntry(const StringPiece& name) {
|
||||
ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name) {
|
||||
const auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
less_than_struct_with_name<ResourceEntry>);
|
||||
if (iter != last && name == (*iter)->name) {
|
||||
return iter->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece& name) {
|
||||
ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name) {
|
||||
auto last = entries.end();
|
||||
auto iter = std::lower_bound(entries.begin(), last, name,
|
||||
lessThanStructWithName<ResourceEntry>);
|
||||
less_than_struct_with_name<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());
|
||||
ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config) {
|
||||
return FindValue(config, StringPiece());
|
||||
}
|
||||
|
||||
struct ConfigKey {
|
||||
@@ -144,7 +147,7 @@ bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs,
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
|
||||
ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{&config, product}, ltConfigKeyRef);
|
||||
@@ -157,7 +160,7 @@ ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findOrCreateValue(
|
||||
ResourceConfigValue* ResourceEntry::FindOrCreateValue(
|
||||
const ConfigDescription& config, const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{&config, product}, ltConfigKeyRef);
|
||||
@@ -197,7 +200,7 @@ std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(
|
||||
return results;
|
||||
}
|
||||
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::FindValuesIf(
|
||||
const std::function<bool(ResourceConfigValue*)>& f) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
for (auto& configValue : values) {
|
||||
@@ -232,16 +235,16 @@ std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
|
||||
* format for there to be
|
||||
* no error.
|
||||
*/
|
||||
ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(
|
||||
Value* existing, Value* incoming) {
|
||||
Attribute* existingAttr = valueCast<Attribute>(existing);
|
||||
Attribute* incomingAttr = valueCast<Attribute>(incoming);
|
||||
if (!incomingAttr) {
|
||||
if (incoming->isWeak()) {
|
||||
Attribute* existing_attr = ValueCast<Attribute>(existing);
|
||||
Attribute* incoming_attr = ValueCast<Attribute>(incoming);
|
||||
if (!incoming_attr) {
|
||||
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()) {
|
||||
} else if (existing->IsWeak()) {
|
||||
// Override the weak resource with the new strong resource.
|
||||
return CollisionResult::kTakeNew;
|
||||
}
|
||||
@@ -250,8 +253,8 @@ ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
if (!existingAttr) {
|
||||
if (existing->isWeak()) {
|
||||
if (!existing_attr) {
|
||||
if (existing->IsWeak()) {
|
||||
// The existing value is not an attribute and it is weak,
|
||||
// so take the incoming attribute value.
|
||||
return CollisionResult::kTakeNew;
|
||||
@@ -261,7 +264,7 @@ ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
assert(incomingAttr && existingAttr);
|
||||
CHECK(incoming_attr != nullptr && existing_attr != nullptr);
|
||||
|
||||
//
|
||||
// Attribute specific handling. At this point we know both
|
||||
@@ -269,22 +272,22 @@ ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
// attributes all-over, we do special handling to see
|
||||
// which definition sticks.
|
||||
//
|
||||
if (existingAttr->typeMask == incomingAttr->typeMask) {
|
||||
if (existing_attr->type_mask == incoming_attr->type_mask) {
|
||||
// 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;
|
||||
return existing_attr->IsWeak() ? CollisionResult::kTakeNew
|
||||
: CollisionResult::kKeepOriginal;
|
||||
}
|
||||
|
||||
if (existingAttr->isWeak() &&
|
||||
existingAttr->typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
if (existing_attr->IsWeak() &&
|
||||
existing_attr->type_mask == 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) {
|
||||
if (incoming_attr->IsWeak() &&
|
||||
incoming_attr->type_mask == android::ResTable_map::TYPE_ANY) {
|
||||
// The incoming attribute may be a USE instead of a DECL.
|
||||
// Keep the existing attribute.
|
||||
return CollisionResult::kKeepOriginal;
|
||||
@@ -295,138 +298,139 @@ ResourceTable::CollisionResult ResourceTable::resolveValueCollision(
|
||||
static constexpr const char* kValidNameChars = "._-";
|
||||
static constexpr const char* kValidNameMangledChars = "._-$";
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
bool ResourceTable::AddResource(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
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,
|
||||
const ResourceId& resId,
|
||||
bool ResourceTable::AddResource(const ResourceNameRef& name,
|
||||
const ResourceId& res_id,
|
||||
const ConfigDescription& config,
|
||||
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, res_id, config, product, std::move(value),
|
||||
kValidNameChars, ResolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
bool ResourceTable::AddFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece& path,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReferenceImpl(name, config, source, path, nullptr,
|
||||
return AddFileReferenceImpl(name, config, source, path, nullptr,
|
||||
kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReferenceAllowMangled(
|
||||
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,
|
||||
return AddFileReferenceImpl(name, config, source, path, file,
|
||||
kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReferenceImpl(
|
||||
bool ResourceTable::AddFileReferenceImpl(
|
||||
const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece& path, io::IFile* file,
|
||||
const char* validChars, IDiagnostics* diag) {
|
||||
const char* valid_chars, IDiagnostics* diag) {
|
||||
std::unique_ptr<FileReference> fileRef =
|
||||
util::make_unique<FileReference>(stringPool.makeRef(path));
|
||||
fileRef->setSource(source);
|
||||
util::make_unique<FileReference>(string_pool.MakeRef(path));
|
||||
fileRef->SetSource(source);
|
||||
fileRef->file = file;
|
||||
return addResourceImpl(name, ResourceId{}, config, StringPiece{},
|
||||
std::move(fileRef), validChars, resolveValueCollision,
|
||||
return AddResourceImpl(name, ResourceId{}, config, StringPiece{},
|
||||
std::move(fileRef), valid_chars, ResolveValueCollision,
|
||||
diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
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,
|
||||
bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& id,
|
||||
const ConfigDescription& config,
|
||||
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,
|
||||
bool ResourceTable::AddResourceImpl(
|
||||
const ResourceNameRef& name, const ResourceId& res_id,
|
||||
const ConfigDescription& config, const StringPiece& product,
|
||||
std::unique_ptr<Value> value, const char* validChars,
|
||||
std::unique_ptr<Value> value, const char* valid_chars,
|
||||
const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
|
||||
assert(value && "value can't be nullptr");
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
CHECK(value != nullptr);
|
||||
CHECK(diag != nullptr);
|
||||
|
||||
auto badCharIter =
|
||||
util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
auto bad_char_iter =
|
||||
util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
|
||||
if (bad_char_iter != name.entry.end()) {
|
||||
diag->Error(DiagMessage(value->GetSource())
|
||||
<< "resource '" << name << "' has invalid entry name '"
|
||||
<< name.entry << "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1) << "'");
|
||||
<< StringPiece(bad_char_iter, 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
|
||||
ResourceTablePackage* package = FindOrCreatePackage(name.package);
|
||||
if (res_id.is_valid() && package->id &&
|
||||
package->id.value() != res_id.package_id()) {
|
||||
diag->Error(DiagMessage(value->GetSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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
|
||||
ResourceTableType* type = package->FindOrCreateType(name.type);
|
||||
if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
|
||||
diag->Error(DiagMessage(value->GetSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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
|
||||
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
|
||||
if (res_id.is_valid() && entry->id &&
|
||||
entry->id.value() != res_id.entry_id()) {
|
||||
diag->Error(DiagMessage(value->GetSource())
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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) {
|
||||
ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
|
||||
if (!config_value->value) {
|
||||
// Resource does not exist, add it now.
|
||||
configValue->value = std::move(value);
|
||||
config_value->value = std::move(value);
|
||||
|
||||
} else {
|
||||
switch (conflictResolver(configValue->value.get(), value.get())) {
|
||||
switch (conflictResolver(config_value->value.get(), value.get())) {
|
||||
case CollisionResult::kTakeNew:
|
||||
// Take the incoming value.
|
||||
configValue->value = std::move(value);
|
||||
config_value->value = std::move(value);
|
||||
break;
|
||||
|
||||
case CollisionResult::kConflict:
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
diag->Error(DiagMessage(value->GetSource())
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << config << "'");
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
diag->Error(DiagMessage(config_value->value->GetSource())
|
||||
<< "resource previously defined here");
|
||||
return false;
|
||||
|
||||
@@ -435,113 +439,114 @@ bool ResourceTable::addResourceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if (resId.isValid()) {
|
||||
package->id = resId.packageId();
|
||||
type->id = resId.typeId();
|
||||
entry->id = resId.entryId();
|
||||
if (res_id.is_valid()) {
|
||||
package->id = res_id.package_id();
|
||||
type->id = res_id.type_id();
|
||||
entry->id = res_id.entry_id();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolState(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
bool ResourceTable::SetSymbolState(const ResourceNameRef& name,
|
||||
const ResourceId& res_id,
|
||||
const Symbol& symbol, IDiagnostics* diag) {
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
|
||||
return SetSymbolStateImpl(name, res_id, symbol, kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& res_id,
|
||||
const Symbol& symbol,
|
||||
IDiagnostics* diag) {
|
||||
return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
|
||||
return SetSymbolStateImpl(name, res_id, symbol, kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name,
|
||||
const ResourceId& resId,
|
||||
bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name,
|
||||
const ResourceId& res_id,
|
||||
const Symbol& symbol,
|
||||
const char* validChars,
|
||||
const char* valid_chars,
|
||||
IDiagnostics* diag) {
|
||||
assert(diag && "diagnostics can't be nullptr");
|
||||
CHECK(diag != nullptr);
|
||||
|
||||
auto badCharIter =
|
||||
util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
diag->error(DiagMessage(symbol.source)
|
||||
auto bad_char_iter =
|
||||
util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
|
||||
if (bad_char_iter != name.entry.end()) {
|
||||
diag->Error(DiagMessage(symbol.source)
|
||||
<< "resource '" << name << "' has invalid entry name '"
|
||||
<< name.entry << "'. Invalid character '"
|
||||
<< StringPiece(badCharIter, 1) << "'");
|
||||
<< StringPiece(bad_char_iter, 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
|
||||
ResourceTablePackage* package = FindOrCreatePackage(name.package);
|
||||
if (res_id.is_valid() && package->id &&
|
||||
package->id.value() != res_id.package_id()) {
|
||||
diag->Error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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
|
||||
ResourceTableType* type = package->FindOrCreateType(name.type);
|
||||
if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) {
|
||||
diag->Error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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
|
||||
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
|
||||
if (res_id.is_valid() && entry->id &&
|
||||
entry->id.value() != res_id.entry_id()) {
|
||||
diag->Error(DiagMessage(symbol.source)
|
||||
<< "trying to add resource '" << name << "' with ID " << res_id
|
||||
<< " 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 (res_id.is_valid()) {
|
||||
package->id = res_id.package_id();
|
||||
type->id = res_id.type_id();
|
||||
entry->id = res_id.entry_id();
|
||||
}
|
||||
|
||||
// Only mark the type state as public, it doesn't care about being private.
|
||||
if (symbol.state == SymbolState::kPublic) {
|
||||
type->symbolStatus.state = SymbolState::kPublic;
|
||||
type->symbol_status.state = SymbolState::kPublic;
|
||||
}
|
||||
|
||||
if (symbol.state == SymbolState::kUndefined &&
|
||||
entry->symbolStatus.state != SymbolState::kUndefined) {
|
||||
entry->symbol_status.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) {
|
||||
entry->symbol_status.state == SymbolState::kPublic) {
|
||||
// We can't downgrade public to private. Ignore.
|
||||
return true;
|
||||
}
|
||||
|
||||
entry->symbolStatus = std::move(symbol);
|
||||
entry->symbol_status = std::move(symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<ResourceTable::SearchResult> ResourceTable::findResource(
|
||||
Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(
|
||||
const ResourceNameRef& name) {
|
||||
ResourceTablePackage* package = findPackage(name.package);
|
||||
ResourceTablePackage* package = FindPackage(name.package);
|
||||
if (!package) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourceTableType* type = package->findType(name.type);
|
||||
ResourceTableType* type = package->FindType(name.type);
|
||||
if (!type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourceEntry* entry = type->findEntry(name.entry);
|
||||
ResourceEntry* entry = type->FindEntry(name.entry);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ResourceConfigValue {
|
||||
|
||||
ResourceConfigValue(const ConfigDescription& config,
|
||||
const StringPiece& product)
|
||||
: config(config), product(product.toString()) {}
|
||||
: config(config), product(product.ToString()) {}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
|
||||
@@ -98,23 +98,23 @@ class ResourceEntry {
|
||||
* Whether this resource is public (and must maintain the same entry ID across
|
||||
* builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
Symbol symbol_status;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
ResourceConfigValue* FindValue(const ConfigDescription& config);
|
||||
ResourceConfigValue* FindValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
|
||||
ResourceConfigValue* FindOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
std::vector<ResourceConfigValue*> findAllValues(
|
||||
const ConfigDescription& config);
|
||||
std::vector<ResourceConfigValue*> findValuesIf(
|
||||
std::vector<ResourceConfigValue*> FindValuesIf(
|
||||
const std::function<bool(ResourceConfigValue*)>& f);
|
||||
|
||||
private:
|
||||
@@ -141,7 +141,7 @@ class ResourceTableType {
|
||||
* Whether this type is public (and must maintain the same
|
||||
* type ID across builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
Symbol symbol_status;
|
||||
|
||||
/**
|
||||
* List of resources for this type.
|
||||
@@ -150,8 +150,8 @@ class ResourceTableType {
|
||||
|
||||
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);
|
||||
@@ -168,8 +168,8 @@ class ResourceTablePackage {
|
||||
std::vector<std::unique_ptr<ResourceTableType>> types;
|
||||
|
||||
ResourceTablePackage() = default;
|
||||
ResourceTableType* findType(ResourceType type);
|
||||
ResourceTableType* findOrCreateType(const ResourceType type);
|
||||
ResourceTableType* FindType(ResourceType type);
|
||||
ResourceTableType* FindOrCreateType(const ResourceType type);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
|
||||
@@ -191,53 +191,53 @@ class ResourceTable {
|
||||
* When a collision of resources occurs, this method decides which value to
|
||||
* keep.
|
||||
*/
|
||||
static CollisionResult resolveValueCollision(Value* existing,
|
||||
static CollisionResult ResolveValueCollision(Value* existing,
|
||||
Value* incoming);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
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,
|
||||
bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
|
||||
const ConfigDescription& config, const StringPiece& product,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
bool AddFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const Source& source,
|
||||
const StringPiece& path, IDiagnostics* diag);
|
||||
|
||||
bool addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
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
|
||||
* 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,
|
||||
bool AddResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
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,
|
||||
bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
|
||||
const Symbol& symbol, IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& resId, const Symbol& symbol,
|
||||
IDiagnostics* diag);
|
||||
bool SetSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId& res_id,
|
||||
const Symbol& symbol, IDiagnostics* diag);
|
||||
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
@@ -245,21 +245,21 @@ class ResourceTable {
|
||||
ResourceEntry* entry;
|
||||
};
|
||||
|
||||
Maybe<SearchResult> findResource(const ResourceNameRef& name);
|
||||
Maybe<SearchResult> FindResource(const ResourceNameRef& name);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* NOTE: `string_pool` must come before `packages` so that it is destroyed
|
||||
* after.
|
||||
* When `string pool` references are destroyed (as they will be when
|
||||
* 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;
|
||||
StringPool string_pool;
|
||||
|
||||
/**
|
||||
* The list of packages in this table, sorted alphabetically by package name.
|
||||
@@ -273,31 +273,31 @@ class ResourceTable {
|
||||
* represent the
|
||||
* 'current' package before it is known to the ResourceTable.
|
||||
*/
|
||||
ResourceTablePackage* findPackage(const StringPiece& name);
|
||||
ResourceTablePackage* FindPackage(const StringPiece& name);
|
||||
|
||||
ResourceTablePackage* findPackageById(uint8_t id);
|
||||
ResourceTablePackage* FindPackageById(uint8_t id);
|
||||
|
||||
ResourceTablePackage* createPackage(const StringPiece& name,
|
||||
ResourceTablePackage* CreatePackage(const StringPiece& name,
|
||||
Maybe<uint8_t> id = {});
|
||||
|
||||
private:
|
||||
ResourceTablePackage* findOrCreatePackage(const StringPiece& name);
|
||||
ResourceTablePackage* FindOrCreatePackage(const StringPiece& name);
|
||||
|
||||
bool addResourceImpl(const ResourceNameRef& name, const ResourceId& resId,
|
||||
bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product, std::unique_ptr<Value> value,
|
||||
const char* validChars,
|
||||
const CollisionResolverFunc& conflictResolver,
|
||||
const char* valid_chars,
|
||||
const CollisionResolverFunc& conflict_resolver,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReferenceImpl(const ResourceNameRef& name,
|
||||
bool AddFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source, const StringPiece& path,
|
||||
io::IFile* file, const char* validChars,
|
||||
io::IFile* file, const char* valid_chars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId,
|
||||
const Symbol& symbol, const char* validChars,
|
||||
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
|
||||
const Symbol& symbol, const char* valid_chars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
|
||||
|
||||
@@ -29,108 +29,108 @@ namespace aapt {
|
||||
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
ConfigDescription config;
|
||||
ConfigDescription languageConfig;
|
||||
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
|
||||
ConfigDescription language_config;
|
||||
memcpy(language_config.language, "pl", sizeof(language_config.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, "",
|
||||
EXPECT_TRUE(table.AddResource(
|
||||
test::ParseNameOrDie("android:string/ok"), language_config, "",
|
||||
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
|
||||
.setSource("test/path/file.xml", 20u)
|
||||
.build(),
|
||||
test::getDiagnostics()));
|
||||
.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", language_config));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
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");
|
||||
Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
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");
|
||||
attr = test::GetValue<Attribute>(&table, "android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_FALSE(attr->isWeak());
|
||||
EXPECT_FALSE(attr->IsWeak());
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, ProductVaryingValues) {
|
||||
ResourceTable table;
|
||||
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
|
||||
test::parseConfigOrDie("land"), "tablet",
|
||||
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",
|
||||
test::GetDiagnostics()));
|
||||
EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"),
|
||||
test::ParseConfigOrDie("land"), "phone",
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
test::GetDiagnostics()));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
|
||||
&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"), "tablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(
|
||||
test::ParseConfigOrDie("land"), "tablet"));
|
||||
EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
|
||||
&table, "android:string/foo",
|
||||
test::parseConfigOrDie("land"), "phone"));
|
||||
test::ParseConfigOrDie("land"), "phone"));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> sr =
|
||||
table.findResource(test::parseNameOrDie("android:string/foo"));
|
||||
table.FindResource(test::ParseNameOrDie("android:string/foo"));
|
||||
AAPT_ASSERT_TRUE(sr);
|
||||
std::vector<ResourceConfigValue*> values =
|
||||
sr.value().entry->findAllValues(test::parseConfigOrDie("land"));
|
||||
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);
|
||||
|
||||
@@ -15,34 +15,36 @@
|
||||
*/
|
||||
|
||||
#include "ResourceUtils.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "NameMangler.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace aapt {
|
||||
namespace ResourceUtils {
|
||||
|
||||
Maybe<ResourceName> toResourceName(
|
||||
const android::ResTable::resource_name& nameIn) {
|
||||
ResourceName nameOut;
|
||||
if (!nameIn.package) {
|
||||
Maybe<ResourceName> ToResourceName(
|
||||
const android::ResTable::resource_name& name_in) {
|
||||
ResourceName name_out;
|
||||
if (!name_in.package) {
|
||||
return {};
|
||||
}
|
||||
|
||||
nameOut.package =
|
||||
util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen));
|
||||
name_out.package =
|
||||
util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen));
|
||||
|
||||
const ResourceType* type;
|
||||
if (nameIn.type) {
|
||||
type = parseResourceType(
|
||||
util::utf16ToUtf8(StringPiece16(nameIn.type, nameIn.typeLen)));
|
||||
} else if (nameIn.type8) {
|
||||
type = parseResourceType(StringPiece(nameIn.type8, nameIn.typeLen));
|
||||
if (name_in.type) {
|
||||
type = ParseResourceType(
|
||||
util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen)));
|
||||
} else if (name_in.type8) {
|
||||
type = ParseResourceType(StringPiece(name_in.type8, name_in.typeLen));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@@ -51,46 +53,46 @@ Maybe<ResourceName> toResourceName(
|
||||
return {};
|
||||
}
|
||||
|
||||
nameOut.type = *type;
|
||||
name_out.type = *type;
|
||||
|
||||
if (nameIn.name) {
|
||||
nameOut.entry =
|
||||
util::utf16ToUtf8(StringPiece16(nameIn.name, nameIn.nameLen));
|
||||
} else if (nameIn.name8) {
|
||||
nameOut.entry = StringPiece(nameIn.name8, nameIn.nameLen).toString();
|
||||
if (name_in.name) {
|
||||
name_out.entry =
|
||||
util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen));
|
||||
} else if (name_in.name8) {
|
||||
name_out.entry = StringPiece(name_in.name8, name_in.nameLen).ToString();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
return nameOut;
|
||||
return name_out;
|
||||
}
|
||||
|
||||
bool extractResourceName(const StringPiece& str, StringPiece* outPackage,
|
||||
StringPiece* outType, StringPiece* outEntry) {
|
||||
bool hasPackageSeparator = false;
|
||||
bool hasTypeSeparator = false;
|
||||
bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
|
||||
StringPiece* out_type, StringPiece* out_entry) {
|
||||
bool has_package_separator = false;
|
||||
bool has_type_separator = false;
|
||||
const char* start = str.data();
|
||||
const char* end = start + str.size();
|
||||
const char* current = start;
|
||||
while (current != end) {
|
||||
if (outType->size() == 0 && *current == '/') {
|
||||
hasTypeSeparator = true;
|
||||
outType->assign(start, current - start);
|
||||
if (out_type->size() == 0 && *current == '/') {
|
||||
has_type_separator = true;
|
||||
out_type->assign(start, current - start);
|
||||
start = current + 1;
|
||||
} else if (outPackage->size() == 0 && *current == ':') {
|
||||
hasPackageSeparator = true;
|
||||
outPackage->assign(start, current - start);
|
||||
} else if (out_package->size() == 0 && *current == ':') {
|
||||
has_package_separator = true;
|
||||
out_package->assign(start, current - start);
|
||||
start = current + 1;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
outEntry->assign(start, end - start);
|
||||
out_entry->assign(start, end - start);
|
||||
|
||||
return !(hasPackageSeparator && outPackage->empty()) &&
|
||||
!(hasTypeSeparator && outType->empty());
|
||||
return !(has_package_separator && out_package->empty()) &&
|
||||
!(has_type_separator && out_type->empty());
|
||||
}
|
||||
|
||||
bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef,
|
||||
bool* outPrivate) {
|
||||
bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
|
||||
bool* out_private) {
|
||||
if (str.empty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -105,13 +107,13 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef,
|
||||
StringPiece package;
|
||||
StringPiece type;
|
||||
StringPiece entry;
|
||||
if (!extractResourceName(str.substr(offset, str.size() - offset), &package,
|
||||
if (!ExtractResourceName(str.substr(offset, str.size() - offset), &package,
|
||||
&type, &entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResourceType* parsedType = parseResourceType(type);
|
||||
if (!parsedType) {
|
||||
const ResourceType* parsed_type = ParseResourceType(type);
|
||||
if (!parsed_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,37 +121,37 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outRef) {
|
||||
outRef->package = package;
|
||||
outRef->type = *parsedType;
|
||||
outRef->entry = entry;
|
||||
if (out_ref) {
|
||||
out_ref->package = package;
|
||||
out_ref->type = *parsed_type;
|
||||
out_ref->entry = entry;
|
||||
}
|
||||
|
||||
if (outPrivate) {
|
||||
*outPrivate = priv;
|
||||
if (out_private) {
|
||||
*out_private = priv;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseReference(const StringPiece& str, ResourceNameRef* outRef,
|
||||
bool* outCreate, bool* outPrivate) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
if (trimmedStr.empty()) {
|
||||
bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
|
||||
bool* out_create, bool* out_private) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
if (trimmed_str.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool create = false;
|
||||
bool priv = false;
|
||||
if (trimmedStr.data()[0] == '@') {
|
||||
if (trimmed_str.data()[0] == '@') {
|
||||
size_t offset = 1;
|
||||
if (trimmedStr.data()[1] == '+') {
|
||||
if (trimmed_str.data()[1] == '+') {
|
||||
create = true;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
ResourceNameRef name;
|
||||
if (!parseResourceName(
|
||||
trimmedStr.substr(offset, trimmedStr.size() - offset), &name,
|
||||
if (!ParseResourceName(
|
||||
trimmed_str.substr(offset, trimmed_str.size() - offset), &name,
|
||||
&priv)) {
|
||||
return false;
|
||||
}
|
||||
@@ -162,37 +164,37 @@ bool parseReference(const StringPiece& str, ResourceNameRef* outRef,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outRef) {
|
||||
*outRef = name;
|
||||
if (out_ref) {
|
||||
*out_ref = name;
|
||||
}
|
||||
|
||||
if (outCreate) {
|
||||
*outCreate = create;
|
||||
if (out_create) {
|
||||
*out_create = create;
|
||||
}
|
||||
|
||||
if (outPrivate) {
|
||||
*outPrivate = priv;
|
||||
if (out_private) {
|
||||
*out_private = priv;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isReference(const StringPiece& str) {
|
||||
return parseReference(str, nullptr, nullptr, nullptr);
|
||||
bool IsReference(const StringPiece& str) {
|
||||
return ParseReference(str, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
if (trimmedStr.empty()) {
|
||||
bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
if (trimmed_str.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*trimmedStr.data() == '?') {
|
||||
if (*trimmed_str.data() == '?') {
|
||||
StringPiece package;
|
||||
StringPiece type;
|
||||
StringPiece entry;
|
||||
if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
|
||||
if (!ExtractResourceName(trimmed_str.substr(1, trimmed_str.size() - 1),
|
||||
&package, &type, &entry)) {
|
||||
return false;
|
||||
}
|
||||
@@ -205,18 +207,18 @@ bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outRef) {
|
||||
outRef->package = package;
|
||||
outRef->type = ResourceType::kAttr;
|
||||
outRef->entry = entry;
|
||||
if (out_ref) {
|
||||
out_ref->package = package;
|
||||
out_ref->type = ResourceType::kAttr;
|
||||
out_ref->entry = entry;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAttributeReference(const StringPiece& str) {
|
||||
return parseAttributeReference(str, nullptr);
|
||||
bool IsAttributeReference(const StringPiece& str) {
|
||||
return ParseAttributeReference(str, nullptr);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -227,65 +229,65 @@ bool isAttributeReference(const StringPiece& str) {
|
||||
* <[*]package>:[style/]<entry>
|
||||
* [[*]package:style/]<entry>
|
||||
*/
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece& str,
|
||||
std::string* outError) {
|
||||
Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
|
||||
std::string* out_error) {
|
||||
if (str.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
StringPiece name = str;
|
||||
|
||||
bool hasLeadingIdentifiers = false;
|
||||
bool privateRef = false;
|
||||
bool has_leading_identifiers = false;
|
||||
bool private_ref = false;
|
||||
|
||||
// Skip over these identifiers. A style's parent is a normal reference.
|
||||
if (name.data()[0] == '@' || name.data()[0] == '?') {
|
||||
hasLeadingIdentifiers = true;
|
||||
has_leading_identifiers = true;
|
||||
name = name.substr(1, name.size() - 1);
|
||||
}
|
||||
|
||||
if (name.data()[0] == '*') {
|
||||
privateRef = true;
|
||||
private_ref = true;
|
||||
name = name.substr(1, name.size() - 1);
|
||||
}
|
||||
|
||||
ResourceNameRef ref;
|
||||
ref.type = ResourceType::kStyle;
|
||||
|
||||
StringPiece typeStr;
|
||||
extractResourceName(name, &ref.package, &typeStr, &ref.entry);
|
||||
if (!typeStr.empty()) {
|
||||
StringPiece type_str;
|
||||
ExtractResourceName(name, &ref.package, &type_str, &ref.entry);
|
||||
if (!type_str.empty()) {
|
||||
// If we have a type, make sure it is a Style.
|
||||
const ResourceType* parsedType = parseResourceType(typeStr);
|
||||
if (!parsedType || *parsedType != ResourceType::kStyle) {
|
||||
const ResourceType* parsed_type = ParseResourceType(type_str);
|
||||
if (!parsed_type || *parsed_type != ResourceType::kStyle) {
|
||||
std::stringstream err;
|
||||
err << "invalid resource type '" << typeStr << "' for parent of style";
|
||||
*outError = err.str();
|
||||
err << "invalid resource type '" << type_str << "' for parent of style";
|
||||
*out_error = err.str();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
|
||||
if (!has_leading_identifiers && ref.package.empty() && !type_str.empty()) {
|
||||
std::stringstream err;
|
||||
err << "invalid parent reference '" << str << "'";
|
||||
*outError = err.str();
|
||||
*out_error = err.str();
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference result(ref);
|
||||
result.privateReference = privateRef;
|
||||
result.private_reference = private_ref;
|
||||
return result;
|
||||
}
|
||||
|
||||
Maybe<Reference> parseXmlAttributeName(const StringPiece& str) {
|
||||
StringPiece trimmedStr = util::trimWhitespace(str);
|
||||
const char* start = trimmedStr.data();
|
||||
const char* const end = start + trimmedStr.size();
|
||||
Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) {
|
||||
StringPiece trimmed_str = util::TrimWhitespace(str);
|
||||
const char* start = trimmed_str.data();
|
||||
const char* const end = start + trimmed_str.size();
|
||||
const char* p = start;
|
||||
|
||||
Reference ref;
|
||||
if (p != end && *p == '*') {
|
||||
ref.privateReference = true;
|
||||
ref.private_reference = true;
|
||||
start++;
|
||||
p++;
|
||||
}
|
||||
@@ -302,38 +304,38 @@ Maybe<Reference> parseXmlAttributeName(const StringPiece& str) {
|
||||
}
|
||||
|
||||
ref.name =
|
||||
ResourceName(package.toString(), ResourceType::kAttr,
|
||||
name.empty() ? trimmedStr.toString() : name.toString());
|
||||
ResourceName(package.ToString(), ResourceType::kAttr,
|
||||
name.empty() ? trimmed_str.ToString() : name.ToString());
|
||||
return Maybe<Reference>(std::move(ref));
|
||||
}
|
||||
|
||||
std::unique_ptr<Reference> tryParseReference(const StringPiece& str,
|
||||
bool* outCreate) {
|
||||
std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
|
||||
bool* out_create) {
|
||||
ResourceNameRef ref;
|
||||
bool privateRef = false;
|
||||
if (parseReference(str, &ref, outCreate, &privateRef)) {
|
||||
bool private_ref = false;
|
||||
if (ParseReference(str, &ref, out_create, &private_ref)) {
|
||||
std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
|
||||
value->privateReference = privateRef;
|
||||
value->private_reference = private_ref;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (parseAttributeReference(str, &ref)) {
|
||||
if (outCreate) {
|
||||
*outCreate = false;
|
||||
if (ParseAttributeReference(str, &ref)) {
|
||||
if (out_create) {
|
||||
*out_create = false;
|
||||
}
|
||||
return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
android::Res_value value = {};
|
||||
if (trimmedStr == "@null") {
|
||||
if (trimmed_str == "@null") {
|
||||
// TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
|
||||
// Instead we set the data type to TYPE_REFERENCE with a value of 0.
|
||||
value.dataType = android::Res_value::TYPE_REFERENCE;
|
||||
} else if (trimmedStr == "@empty") {
|
||||
} else if (trimmed_str == "@empty") {
|
||||
// TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
|
||||
value.dataType = android::Res_value::TYPE_NULL;
|
||||
value.data = android::Res_value::DATA_NULL_EMPTY;
|
||||
@@ -343,14 +345,14 @@ std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str) {
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
|
||||
const StringPiece& str) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
for (const Attribute::Symbol& symbol : enumAttr->symbols) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
for (const Attribute::Symbol& symbol : enum_attr->symbols) {
|
||||
// Enum symbols are stored as @package:id/symbol resources,
|
||||
// so we need to match against the 'entry' part of the identifier.
|
||||
const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
|
||||
if (trimmedStr == enumSymbolResourceName.entry) {
|
||||
const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value();
|
||||
if (trimmed_str == enum_symbol_resource_name.entry) {
|
||||
android::Res_value value = {};
|
||||
value.dataType = android::Res_value::TYPE_INT_DEC;
|
||||
value.data = symbol.value;
|
||||
@@ -360,40 +362,41 @@ std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
|
||||
std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
|
||||
const StringPiece& str) {
|
||||
android::Res_value flags = {};
|
||||
flags.dataType = android::Res_value::TYPE_INT_HEX;
|
||||
flags.data = 0u;
|
||||
|
||||
if (util::trimWhitespace(str).empty()) {
|
||||
if (util::TrimWhitespace(str).empty()) {
|
||||
// Empty string is a valid flag (0).
|
||||
return util::make_unique<BinaryPrimitive>(flags);
|
||||
}
|
||||
|
||||
for (StringPiece part : util::tokenize(str, '|')) {
|
||||
StringPiece trimmedPart = util::trimWhitespace(part);
|
||||
for (StringPiece part : util::Tokenize(str, '|')) {
|
||||
StringPiece trimmed_part = util::TrimWhitespace(part);
|
||||
|
||||
bool flagSet = false;
|
||||
for (const Attribute::Symbol& symbol : flagAttr->symbols) {
|
||||
bool flag_set = false;
|
||||
for (const Attribute::Symbol& symbol : flag_attr->symbols) {
|
||||
// Flag symbols are stored as @package:id/symbol resources,
|
||||
// so we need to match against the 'entry' part of the identifier.
|
||||
const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
|
||||
if (trimmedPart == flagSymbolResourceName.entry) {
|
||||
const ResourceName& flag_symbol_resource_name =
|
||||
symbol.symbol.name.value();
|
||||
if (trimmed_part == flag_symbol_resource_name.entry) {
|
||||
flags.data |= symbol.value;
|
||||
flagSet = true;
|
||||
flag_set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flagSet) {
|
||||
if (!flag_set) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return util::make_unique<BinaryPrimitive>(flags);
|
||||
}
|
||||
|
||||
static uint32_t parseHex(char c, bool* outError) {
|
||||
static uint32_t ParseHex(char c, bool* out_error) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
@@ -401,15 +404,15 @@ static uint32_t parseHex(char c, bool* outError) {
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 0xa;
|
||||
} else {
|
||||
*outError = true;
|
||||
*out_error = true;
|
||||
return 0xffffffffu;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) {
|
||||
StringPiece colorStr(util::trimWhitespace(str));
|
||||
const char* start = colorStr.data();
|
||||
const size_t len = colorStr.size();
|
||||
std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
|
||||
StringPiece color_str(util::TrimWhitespace(str));
|
||||
const char* start = color_str.data();
|
||||
const size_t len = color_str.size();
|
||||
if (len == 0 || start[0] != '#') {
|
||||
return {};
|
||||
}
|
||||
@@ -419,41 +422,41 @@ std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) {
|
||||
if (len == 4) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
|
||||
value.data = 0xff000000u;
|
||||
value.data |= parseHex(start[1], &error) << 20;
|
||||
value.data |= parseHex(start[1], &error) << 16;
|
||||
value.data |= parseHex(start[2], &error) << 12;
|
||||
value.data |= parseHex(start[2], &error) << 8;
|
||||
value.data |= parseHex(start[3], &error) << 4;
|
||||
value.data |= parseHex(start[3], &error);
|
||||
value.data |= ParseHex(start[1], &error) << 20;
|
||||
value.data |= ParseHex(start[1], &error) << 16;
|
||||
value.data |= ParseHex(start[2], &error) << 12;
|
||||
value.data |= ParseHex(start[2], &error) << 8;
|
||||
value.data |= ParseHex(start[3], &error) << 4;
|
||||
value.data |= ParseHex(start[3], &error);
|
||||
} else if (len == 5) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
|
||||
value.data |= parseHex(start[1], &error) << 28;
|
||||
value.data |= parseHex(start[1], &error) << 24;
|
||||
value.data |= parseHex(start[2], &error) << 20;
|
||||
value.data |= parseHex(start[2], &error) << 16;
|
||||
value.data |= parseHex(start[3], &error) << 12;
|
||||
value.data |= parseHex(start[3], &error) << 8;
|
||||
value.data |= parseHex(start[4], &error) << 4;
|
||||
value.data |= parseHex(start[4], &error);
|
||||
value.data |= ParseHex(start[1], &error) << 28;
|
||||
value.data |= ParseHex(start[1], &error) << 24;
|
||||
value.data |= ParseHex(start[2], &error) << 20;
|
||||
value.data |= ParseHex(start[2], &error) << 16;
|
||||
value.data |= ParseHex(start[3], &error) << 12;
|
||||
value.data |= ParseHex(start[3], &error) << 8;
|
||||
value.data |= ParseHex(start[4], &error) << 4;
|
||||
value.data |= ParseHex(start[4], &error);
|
||||
} else if (len == 7) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
|
||||
value.data = 0xff000000u;
|
||||
value.data |= parseHex(start[1], &error) << 20;
|
||||
value.data |= parseHex(start[2], &error) << 16;
|
||||
value.data |= parseHex(start[3], &error) << 12;
|
||||
value.data |= parseHex(start[4], &error) << 8;
|
||||
value.data |= parseHex(start[5], &error) << 4;
|
||||
value.data |= parseHex(start[6], &error);
|
||||
value.data |= ParseHex(start[1], &error) << 20;
|
||||
value.data |= ParseHex(start[2], &error) << 16;
|
||||
value.data |= ParseHex(start[3], &error) << 12;
|
||||
value.data |= ParseHex(start[4], &error) << 8;
|
||||
value.data |= ParseHex(start[5], &error) << 4;
|
||||
value.data |= ParseHex(start[6], &error);
|
||||
} else if (len == 9) {
|
||||
value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
|
||||
value.data |= parseHex(start[1], &error) << 28;
|
||||
value.data |= parseHex(start[2], &error) << 24;
|
||||
value.data |= parseHex(start[3], &error) << 20;
|
||||
value.data |= parseHex(start[4], &error) << 16;
|
||||
value.data |= parseHex(start[5], &error) << 12;
|
||||
value.data |= parseHex(start[6], &error) << 8;
|
||||
value.data |= parseHex(start[7], &error) << 4;
|
||||
value.data |= parseHex(start[8], &error);
|
||||
value.data |= ParseHex(start[1], &error) << 28;
|
||||
value.data |= ParseHex(start[2], &error) << 24;
|
||||
value.data |= ParseHex(start[3], &error) << 20;
|
||||
value.data |= ParseHex(start[4], &error) << 16;
|
||||
value.data |= ParseHex(start[5], &error) << 12;
|
||||
value.data |= ParseHex(start[6], &error) << 8;
|
||||
value.data |= ParseHex(start[7], &error) << 4;
|
||||
value.data |= ParseHex(start[8], &error);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@@ -461,19 +464,19 @@ std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) {
|
||||
: util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
Maybe<bool> parseBool(const StringPiece& str) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") {
|
||||
Maybe<bool> ParseBool(const StringPiece& str) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
|
||||
return Maybe<bool>(true);
|
||||
} else if (trimmedStr == "false" || trimmedStr == "FALSE" ||
|
||||
trimmedStr == "False") {
|
||||
} else if (trimmed_str == "false" || trimmed_str == "FALSE" ||
|
||||
trimmed_str == "False") {
|
||||
return Maybe<bool>(false);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<uint32_t> parseInt(const StringPiece& str) {
|
||||
std::u16string str16 = util::utf8ToUtf16(str);
|
||||
Maybe<uint32_t> ParseInt(const StringPiece& str) {
|
||||
std::u16string str16 = util::Utf8ToUtf16(str);
|
||||
android::Res_value value;
|
||||
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
|
||||
return value.data;
|
||||
@@ -481,15 +484,15 @@ Maybe<uint32_t> parseInt(const StringPiece& str) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<ResourceId> parseResourceId(const StringPiece& str) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
Maybe<ResourceId> ParseResourceId(const StringPiece& str) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
|
||||
std::u16string str16 = util::utf8ToUtf16(trimmedStr);
|
||||
std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
|
||||
android::Res_value value;
|
||||
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
|
||||
if (value.dataType == android::Res_value::TYPE_INT_HEX) {
|
||||
ResourceId id(value.data);
|
||||
if (id.isValid()) {
|
||||
if (id.is_valid()) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -497,29 +500,29 @@ Maybe<ResourceId> parseResourceId(const StringPiece& str) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<int> parseSdkVersion(const StringPiece& str) {
|
||||
StringPiece trimmedStr(util::trimWhitespace(str));
|
||||
Maybe<int> ParseSdkVersion(const StringPiece& str) {
|
||||
StringPiece trimmed_str(util::TrimWhitespace(str));
|
||||
|
||||
std::u16string str16 = util::utf8ToUtf16(trimmedStr);
|
||||
std::u16string str16 = util::Utf8ToUtf16(trimmed_str);
|
||||
android::Res_value value;
|
||||
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
|
||||
return static_cast<int>(value.data);
|
||||
}
|
||||
|
||||
// Try parsing the code name.
|
||||
std::pair<StringPiece, int> entry = getDevelopmentSdkCodeNameAndVersion();
|
||||
if (entry.first == trimmedStr) {
|
||||
std::pair<StringPiece, int> entry = GetDevelopmentSdkCodeNameAndVersion();
|
||||
if (entry.first == trimmed_str) {
|
||||
return entry.second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) {
|
||||
if (Maybe<bool> maybeResult = parseBool(str)) {
|
||||
std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
|
||||
if (Maybe<bool> maybe_result = ParseBool(str)) {
|
||||
android::Res_value value = {};
|
||||
value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
|
||||
|
||||
if (maybeResult.value()) {
|
||||
if (maybe_result.value()) {
|
||||
value.data = 0xffffffffu;
|
||||
} else {
|
||||
value.data = 0;
|
||||
@@ -529,8 +532,8 @@ std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str) {
|
||||
std::u16string str16 = util::utf8ToUtf16(str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
|
||||
std::u16string str16 = util::Utf8ToUtf16(str);
|
||||
android::Res_value value;
|
||||
if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
|
||||
return {};
|
||||
@@ -538,8 +541,8 @@ std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str) {
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str) {
|
||||
std::u16string str16 = util::utf8ToUtf16(str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
|
||||
std::u16string str16 = util::Utf8ToUtf16(str);
|
||||
android::Res_value value;
|
||||
if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
|
||||
return {};
|
||||
@@ -547,7 +550,7 @@ std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str) {
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
|
||||
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) {
|
||||
switch (type) {
|
||||
case android::Res_value::TYPE_NULL:
|
||||
case android::Res_value::TYPE_REFERENCE:
|
||||
@@ -587,57 +590,57 @@ uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t typeMask,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference) {
|
||||
std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
|
||||
if (nullOrEmpty) {
|
||||
return std::move(nullOrEmpty);
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t type_mask,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference) {
|
||||
std::unique_ptr<BinaryPrimitive> null_or_empty = TryParseNullOrEmpty(value);
|
||||
if (null_or_empty) {
|
||||
return std::move(null_or_empty);
|
||||
}
|
||||
|
||||
bool create = false;
|
||||
std::unique_ptr<Reference> reference = tryParseReference(value, &create);
|
||||
std::unique_ptr<Reference> reference = TryParseReference(value, &create);
|
||||
if (reference) {
|
||||
if (create && onCreateReference) {
|
||||
onCreateReference(reference->name.value());
|
||||
if (create && on_create_reference) {
|
||||
on_create_reference(reference->name.value());
|
||||
}
|
||||
return std::move(reference);
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_COLOR) {
|
||||
if (type_mask & android::ResTable_map::TYPE_COLOR) {
|
||||
// Try parsing this as a color.
|
||||
std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
|
||||
std::unique_ptr<BinaryPrimitive> color = TryParseColor(value);
|
||||
if (color) {
|
||||
return std::move(color);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
if (type_mask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
// Try parsing this as a boolean.
|
||||
std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
|
||||
std::unique_ptr<BinaryPrimitive> boolean = TryParseBool(value);
|
||||
if (boolean) {
|
||||
return std::move(boolean);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_INTEGER) {
|
||||
if (type_mask & android::ResTable_map::TYPE_INTEGER) {
|
||||
// Try parsing this as an integer.
|
||||
std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
|
||||
std::unique_ptr<BinaryPrimitive> integer = TryParseInt(value);
|
||||
if (integer) {
|
||||
return std::move(integer);
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
|
||||
android::ResTable_map::TYPE_DIMENSION |
|
||||
android::ResTable_map::TYPE_FRACTION;
|
||||
if (typeMask & floatMask) {
|
||||
const uint32_t float_mask = android::ResTable_map::TYPE_FLOAT |
|
||||
android::ResTable_map::TYPE_DIMENSION |
|
||||
android::ResTable_map::TYPE_FRACTION;
|
||||
if (type_mask & float_mask) {
|
||||
// Try parsing this as a float.
|
||||
std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
|
||||
if (floatingPoint) {
|
||||
if (typeMask &
|
||||
androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
|
||||
return std::move(floatingPoint);
|
||||
std::unique_ptr<BinaryPrimitive> floating_point = TryParseFloat(value);
|
||||
if (floating_point) {
|
||||
if (type_mask &
|
||||
AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
|
||||
return std::move(floating_point);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,49 +651,49 @@ std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
* We successively try to parse the string as a resource type that the Attribute
|
||||
* allows.
|
||||
*/
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& str, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference) {
|
||||
const uint32_t typeMask = attr->typeMask;
|
||||
const std::function<void(const ResourceName&)>& on_create_reference) {
|
||||
const uint32_t type_mask = attr->type_mask;
|
||||
std::unique_ptr<Item> value =
|
||||
tryParseItemForAttribute(str, typeMask, onCreateReference);
|
||||
TryParseItemForAttribute(str, type_mask, on_create_reference);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_ENUM) {
|
||||
if (type_mask & android::ResTable_map::TYPE_ENUM) {
|
||||
// Try parsing this as an enum.
|
||||
std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
|
||||
if (enumValue) {
|
||||
return std::move(enumValue);
|
||||
std::unique_ptr<BinaryPrimitive> enum_value = TryParseEnumSymbol(attr, str);
|
||||
if (enum_value) {
|
||||
return std::move(enum_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
if (type_mask & android::ResTable_map::TYPE_FLAGS) {
|
||||
// Try parsing this as a flag.
|
||||
std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
|
||||
if (flagValue) {
|
||||
return std::move(flagValue);
|
||||
std::unique_ptr<BinaryPrimitive> flag_value = TryParseFlagSymbol(attr, str);
|
||||
if (flag_value) {
|
||||
return std::move(flag_value);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string buildResourceFileName(const ResourceFile& resFile,
|
||||
std::string BuildResourceFileName(const ResourceFile& res_file,
|
||||
const NameMangler* mangler) {
|
||||
std::stringstream out;
|
||||
out << "res/" << resFile.name.type;
|
||||
if (resFile.config != ConfigDescription{}) {
|
||||
out << "-" << resFile.config;
|
||||
out << "res/" << res_file.name.type;
|
||||
if (res_file.config != ConfigDescription{}) {
|
||||
out << "-" << res_file.config;
|
||||
}
|
||||
out << "/";
|
||||
|
||||
if (mangler && mangler->shouldMangle(resFile.name.package)) {
|
||||
out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
|
||||
if (mangler && mangler->ShouldMangle(res_file.name.package)) {
|
||||
out << NameMangler::MangleEntry(res_file.name.package, res_file.name.entry);
|
||||
} else {
|
||||
out << resFile.name.entry;
|
||||
out << res_file.name.entry;
|
||||
}
|
||||
out << file::getExtension(resFile.source.path);
|
||||
out << file::GetExtension(res_file.source.path);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
#ifndef AAPT_RESOURCEUTILS_H
|
||||
#define AAPT_RESOURCEUTILS_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "NameMangler.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
namespace ResourceUtils {
|
||||
|
||||
@@ -37,76 +37,75 @@ namespace ResourceUtils {
|
||||
* individual extracted piece to verify that the pieces are valid.
|
||||
* Returns false if there was no package but a ':' was present.
|
||||
*/
|
||||
bool extractResourceName(const StringPiece& str, StringPiece* outPackage,
|
||||
StringPiece* outType, StringPiece* outEntry);
|
||||
bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
|
||||
StringPiece* out_type, StringPiece* out_entry);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* `out_resource` set to the parsed resource name and `out_private` set to true
|
||||
* if a '*' prefix was present.
|
||||
*/
|
||||
bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource,
|
||||
bool* outPrivate = nullptr);
|
||||
bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_resource,
|
||||
bool* out_private = nullptr);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as a reference
|
||||
* (@[+][package:]type/name), with
|
||||
* `outReference` set to the parsed reference.
|
||||
* `out_reference` set to the parsed reference.
|
||||
*
|
||||
* If '+' was present in the reference, `outCreate` is set to true.
|
||||
* If '*' was present in the reference, `outPrivate` is set to true.
|
||||
* If '+' was present in the reference, `out_create` is set to true.
|
||||
* If '*' was present in the reference, `out_private` is set to true.
|
||||
*/
|
||||
bool parseReference(const StringPiece& str, ResourceNameRef* outReference,
|
||||
bool* outCreate = nullptr, bool* outPrivate = nullptr);
|
||||
bool ParseReference(const StringPiece& str, ResourceNameRef* out_reference,
|
||||
bool* out_create = nullptr, bool* out_private = nullptr);
|
||||
|
||||
/*
|
||||
* Returns true if the string is in the form of a resource reference
|
||||
* (@[+][package:]type/name).
|
||||
*/
|
||||
bool isReference(const StringPiece& str);
|
||||
bool IsReference(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as an attribute reference
|
||||
* (?[package:][type/]name),
|
||||
* with `outReference` set to the parsed reference.
|
||||
* with `out_reference` set to the parsed reference.
|
||||
*/
|
||||
bool parseAttributeReference(const StringPiece& str,
|
||||
ResourceNameRef* outReference);
|
||||
bool ParseAttributeReference(const StringPiece& str,
|
||||
ResourceNameRef* out_reference);
|
||||
|
||||
/**
|
||||
* Returns true if the string is in the form of an attribute
|
||||
* reference(?[package:][type/]name).
|
||||
*/
|
||||
bool isAttributeReference(const StringPiece& str);
|
||||
bool IsAttributeReference(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
|
||||
*/
|
||||
Maybe<ResourceName> toResourceName(
|
||||
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.
|
||||
*/
|
||||
Maybe<bool> parseBool(const StringPiece& str);
|
||||
Maybe<bool> ParseBool(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Returns a uint32_t if the string is an integer.
|
||||
*/
|
||||
Maybe<uint32_t> parseInt(const StringPiece& str);
|
||||
Maybe<uint32_t> ParseInt(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Returns an ID if it the string represented a valid ID.
|
||||
*/
|
||||
Maybe<ResourceId> parseResourceId(const StringPiece& str);
|
||||
Maybe<ResourceId> ParseResourceId(const StringPiece& str);
|
||||
|
||||
/**
|
||||
* Parses an SDK version, which can be an integer, or a letter from A-Z.
|
||||
*/
|
||||
Maybe<int> parseSdkVersion(const StringPiece& str);
|
||||
Maybe<int> ParseSdkVersion(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a Reference, or None Maybe instance if the string `str` was parsed as
|
||||
@@ -119,8 +118,8 @@ Maybe<int> parseSdkVersion(const StringPiece& str);
|
||||
* ?[package:]style/<entry> or
|
||||
* <package>:[style/]<entry>
|
||||
*/
|
||||
Maybe<Reference> parseStyleParentReference(const StringPiece& str,
|
||||
std::string* outError);
|
||||
Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
|
||||
std::string* out_error);
|
||||
|
||||
/*
|
||||
* Returns a Reference if the string `str` was parsed as a valid XML attribute
|
||||
@@ -129,7 +128,7 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str,
|
||||
*
|
||||
* package:entry
|
||||
*/
|
||||
Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
|
||||
Maybe<Reference> ParseXmlAttributeName(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a Reference object if the string was parsed as a resource or
|
||||
@@ -138,73 +137,68 @@ Maybe<Reference> parseXmlAttributeName(const StringPiece& str);
|
||||
* 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* out_create = nullptr);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string
|
||||
* was parsed
|
||||
* as one.
|
||||
* was parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a color if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
* parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
* parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a floating point number
|
||||
* (float, dimension, etc) if the string was parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str);
|
||||
std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
* parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
|
||||
std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
|
||||
const StringPiece& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was
|
||||
* parsed
|
||||
* as one.
|
||||
* parsed as one.
|
||||
*/
|
||||
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr,
|
||||
std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
|
||||
const StringPiece& str);
|
||||
/*
|
||||
* 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
|
||||
* The callback function on_create_reference is called when the parsed item is a
|
||||
* reference to an ID that must be created (@+id/foo).
|
||||
*/
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& value, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
const std::function<void(const ResourceName&)>& on_create_reference = {});
|
||||
|
||||
std::unique_ptr<Item> tryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t typeMask,
|
||||
const std::function<void(const ResourceName&)>& onCreateReference = {});
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t type_mask,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference = {});
|
||||
|
||||
uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
/**
|
||||
* Returns a string path suitable for use within an APK. The path will look
|
||||
@@ -216,8 +210,8 @@ uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
* the package
|
||||
* requires mangling.
|
||||
*/
|
||||
std::string buildResourceFileName(const ResourceFile& resFile,
|
||||
const NameMangler* mangler);
|
||||
std::string BuildResourceFileName(const ResourceFile& res_file,
|
||||
const NameMangler* mangler = nullptr);
|
||||
|
||||
} // namespace ResourceUtils
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,128 +15,129 @@
|
||||
*/
|
||||
|
||||
#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));
|
||||
bool actual_priv = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual,
|
||||
&actual_priv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
EXPECT_FALSE(actual_priv);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv));
|
||||
ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv));
|
||||
EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_FALSE(actualPriv);
|
||||
EXPECT_FALSE(actual_priv);
|
||||
|
||||
EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual,
|
||||
&actualPriv));
|
||||
EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual,
|
||||
&actual_priv));
|
||||
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
|
||||
EXPECT_TRUE(actualPriv);
|
||||
EXPECT_TRUE(actual_priv);
|
||||
|
||||
EXPECT_FALSE(
|
||||
ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv));
|
||||
ResourceUtils::ParseResourceName(StringPiece(), &actual, &actual_priv));
|
||||
}
|
||||
|
||||
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));
|
||||
bool private_ref = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create,
|
||||
&private_ref));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
EXPECT_FALSE(private_ref);
|
||||
}
|
||||
|
||||
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));
|
||||
bool private_ref = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual,
|
||||
&create, &private_ref));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
EXPECT_FALSE(private_ref);
|
||||
}
|
||||
|
||||
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));
|
||||
bool private_ref = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t",
|
||||
&actual, &create, &private_ref));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
EXPECT_FALSE(private_ref);
|
||||
}
|
||||
|
||||
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));
|
||||
bool private_ref = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual,
|
||||
&create, &private_ref));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
EXPECT_FALSE(private_ref);
|
||||
}
|
||||
|
||||
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));
|
||||
bool private_ref = false;
|
||||
EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual,
|
||||
&create, &private_ref));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
EXPECT_TRUE(private_ref);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
bool private_ref = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual,
|
||||
&create, &private_ref));
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -144,56 +145,58 @@ TEST(ResourceUtilsTest, ParseStyleParentReference) {
|
||||
"foo");
|
||||
const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo");
|
||||
|
||||
std::string errStr;
|
||||
std::string err_str;
|
||||
Maybe<Reference> ref =
|
||||
ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr);
|
||||
ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr);
|
||||
ref =
|
||||
ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("foo", &errStr);
|
||||
ref = ResourceUtils::ParseStyleParentReference("foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
|
||||
|
||||
ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr);
|
||||
ref =
|
||||
ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str);
|
||||
AAPT_ASSERT_TRUE(ref);
|
||||
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
|
||||
EXPECT_TRUE(ref.value().privateReference);
|
||||
EXPECT_TRUE(ref.value().private_reference);
|
||||
}
|
||||
|
||||
TEST(ResourceUtilsTest, ParseEmptyFlag) {
|
||||
std::unique_ptr<Attribute> attr =
|
||||
test::AttributeBuilder(false)
|
||||
.setTypeMask(android::ResTable_map::TYPE_FLAGS)
|
||||
.addItem("one", 0x01)
|
||||
.addItem("two", 0x02)
|
||||
.build();
|
||||
.SetTypeMask(android::ResTable_map::TYPE_FLAGS)
|
||||
.AddItem("one", 0x01)
|
||||
.AddItem("two", 0x02)
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> result =
|
||||
ResourceUtils::tryParseFlagSymbol(attr.get(), "");
|
||||
ResourceUtils::TryParseFlagSymbol(attr.get(), "");
|
||||
ASSERT_NE(nullptr, result);
|
||||
EXPECT_EQ(0u, result->value.data);
|
||||
}
|
||||
|
||||
@@ -15,94 +15,95 @@
|
||||
*/
|
||||
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "Resource.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(RawValueVisitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
void BaseValue<Derived>::Accept(RawValueVisitor* visitor) {
|
||||
visitor->Visit(static_cast<Derived*>(this));
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
|
||||
visitor->visit(static_cast<Derived*>(this));
|
||||
void BaseItem<Derived>::Accept(RawValueVisitor* visitor) {
|
||||
visitor->Visit(static_cast<Derived*>(this));
|
||||
}
|
||||
|
||||
RawString::RawString(const StringPool::Ref& ref) : value(ref) {}
|
||||
|
||||
bool RawString::equals(const Value* value) const {
|
||||
const RawString* other = valueCast<RawString>(value);
|
||||
bool RawString::Equals(const Value* value) const {
|
||||
const RawString* other = ValueCast<RawString>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return *this->value == *other->value;
|
||||
}
|
||||
|
||||
RawString* RawString::clone(StringPool* newPool) const {
|
||||
RawString* rs = new RawString(newPool->makeRef(*value));
|
||||
rs->mComment = mComment;
|
||||
rs->mSource = mSource;
|
||||
RawString* RawString::Clone(StringPool* new_pool) const {
|
||||
RawString* rs = new RawString(new_pool->MakeRef(*value));
|
||||
rs->comment_ = comment_;
|
||||
rs->source_ = source_;
|
||||
return rs;
|
||||
}
|
||||
|
||||
bool RawString::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data =
|
||||
util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
bool RawString::Flatten(android::Res_value* out_value) const {
|
||||
out_value->dataType = android::Res_value::TYPE_STRING;
|
||||
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void RawString::print(std::ostream* out) const {
|
||||
void RawString::Print(std::ostream* out) const {
|
||||
*out << "(raw string) " << *value;
|
||||
}
|
||||
|
||||
Reference::Reference() : referenceType(Type::kResource) {}
|
||||
Reference::Reference() : reference_type(Type::kResource) {}
|
||||
|
||||
Reference::Reference(const ResourceNameRef& n, Type t)
|
||||
: name(n.toResourceName()), referenceType(t) {}
|
||||
: name(n.ToResourceName()), reference_type(t) {}
|
||||
|
||||
Reference::Reference(const ResourceId& i, Type type)
|
||||
: id(i), referenceType(type) {}
|
||||
: id(i), reference_type(type) {}
|
||||
|
||||
Reference::Reference(const ResourceNameRef& n, const ResourceId& i)
|
||||
: name(n.toResourceName()), id(i), referenceType(Type::kResource) {}
|
||||
: name(n.ToResourceName()), id(i), reference_type(Type::kResource) {}
|
||||
|
||||
bool Reference::equals(const Value* value) const {
|
||||
const Reference* other = valueCast<Reference>(value);
|
||||
bool Reference::Equals(const Value* value) const {
|
||||
const Reference* other = ValueCast<Reference>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return referenceType == other->referenceType &&
|
||||
privateReference == other->privateReference && id == other->id &&
|
||||
return reference_type == other->reference_type &&
|
||||
private_reference == other->private_reference && id == other->id &&
|
||||
name == other->name;
|
||||
}
|
||||
|
||||
bool Reference::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = (referenceType == Reference::Type::kResource)
|
||||
? android::Res_value::TYPE_REFERENCE
|
||||
: android::Res_value::TYPE_ATTRIBUTE;
|
||||
outValue->data = util::hostToDevice32(id ? id.value().id : 0);
|
||||
bool Reference::Flatten(android::Res_value* out_value) const {
|
||||
out_value->dataType = (reference_type == Reference::Type::kResource)
|
||||
? android::Res_value::TYPE_REFERENCE
|
||||
: android::Res_value::TYPE_ATTRIBUTE;
|
||||
out_value->data = util::HostToDevice32(id ? id.value().id : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference* Reference::clone(StringPool* /*newPool*/) const {
|
||||
Reference* Reference::Clone(StringPool* /*new_pool*/) const {
|
||||
return new Reference(*this);
|
||||
}
|
||||
|
||||
void Reference::print(std::ostream* out) const {
|
||||
void Reference::Print(std::ostream* out) const {
|
||||
*out << "(reference) ";
|
||||
if (referenceType == Reference::Type::kResource) {
|
||||
if (reference_type == Reference::Type::kResource) {
|
||||
*out << "@";
|
||||
if (privateReference) {
|
||||
if (private_reference) {
|
||||
*out << "*";
|
||||
}
|
||||
} else {
|
||||
@@ -118,128 +119,127 @@ void Reference::print(std::ostream* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
bool Id::equals(const Value* value) const {
|
||||
return valueCast<Id>(value) != nullptr;
|
||||
bool Id::Equals(const Value* value) const {
|
||||
return ValueCast<Id>(value) != nullptr;
|
||||
}
|
||||
|
||||
bool Id::flatten(android::Res_value* out) const {
|
||||
bool Id::Flatten(android::Res_value* out) const {
|
||||
out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
|
||||
out->data = util::hostToDevice32(0);
|
||||
out->data = util::HostToDevice32(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
Id* Id::clone(StringPool* /*newPool*/) const { return new Id(*this); }
|
||||
Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); }
|
||||
|
||||
void Id::print(std::ostream* out) const { *out << "(id)"; }
|
||||
void Id::Print(std::ostream* out) const { *out << "(id)"; }
|
||||
|
||||
String::String(const StringPool::Ref& ref) : value(ref) {}
|
||||
|
||||
bool String::equals(const Value* value) const {
|
||||
const String* other = valueCast<String>(value);
|
||||
bool String::Equals(const Value* value) const {
|
||||
const String* other = ValueCast<String>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return *this->value == *other->value;
|
||||
}
|
||||
|
||||
bool String::flatten(android::Res_value* outValue) const {
|
||||
bool String::Flatten(android::Res_value* out_value) const {
|
||||
// Verify that our StringPool index is within encode-able limits.
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
if (value.index() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data =
|
||||
util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
out_value->dataType = android::Res_value::TYPE_STRING;
|
||||
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
|
||||
return true;
|
||||
}
|
||||
|
||||
String* String::clone(StringPool* newPool) const {
|
||||
String* str = new String(newPool->makeRef(*value));
|
||||
str->mComment = mComment;
|
||||
str->mSource = mSource;
|
||||
String* String::Clone(StringPool* new_pool) const {
|
||||
String* str = new String(new_pool->MakeRef(*value));
|
||||
str->comment_ = comment_;
|
||||
str->source_ = source_;
|
||||
return str;
|
||||
}
|
||||
|
||||
void String::print(std::ostream* out) const {
|
||||
void String::Print(std::ostream* out) const {
|
||||
*out << "(string) \"" << *value << "\"";
|
||||
}
|
||||
|
||||
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
|
||||
|
||||
bool StyledString::equals(const Value* value) const {
|
||||
const StyledString* other = valueCast<StyledString>(value);
|
||||
bool StyledString::Equals(const Value* value) const {
|
||||
const StyledString* other = ValueCast<StyledString>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*this->value->str == *other->value->str) {
|
||||
const std::vector<StringPool::Span>& spansA = this->value->spans;
|
||||
const std::vector<StringPool::Span>& spansB = other->value->spans;
|
||||
const std::vector<StringPool::Span>& spans_a = this->value->spans;
|
||||
const std::vector<StringPool::Span>& spans_b = other->value->spans;
|
||||
return std::equal(
|
||||
spansA.begin(), spansA.end(), spansB.begin(),
|
||||
spans_a.begin(), spans_a.end(), spans_b.begin(),
|
||||
[](const StringPool::Span& a, const StringPool::Span& b) -> bool {
|
||||
return *a.name == *b.name && a.firstChar == b.firstChar &&
|
||||
a.lastChar == b.lastChar;
|
||||
return *a.name == *b.name && a.first_char == b.first_char &&
|
||||
a.last_char == b.last_char;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StyledString::flatten(android::Res_value* outValue) const {
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
bool StyledString::Flatten(android::Res_value* out_value) const {
|
||||
if (value.index() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data =
|
||||
util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
|
||||
out_value->dataType = android::Res_value::TYPE_STRING;
|
||||
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
|
||||
return true;
|
||||
}
|
||||
|
||||
StyledString* StyledString::clone(StringPool* newPool) const {
|
||||
StyledString* str = new StyledString(newPool->makeRef(value));
|
||||
str->mComment = mComment;
|
||||
str->mSource = mSource;
|
||||
StyledString* StyledString::Clone(StringPool* new_pool) const {
|
||||
StyledString* str = new StyledString(new_pool->MakeRef(value));
|
||||
str->comment_ = comment_;
|
||||
str->source_ = source_;
|
||||
return str;
|
||||
}
|
||||
|
||||
void StyledString::print(std::ostream* out) const {
|
||||
void StyledString::Print(std::ostream* out) const {
|
||||
*out << "(styled string) \"" << *value->str << "\"";
|
||||
for (const StringPool::Span& span : value->spans) {
|
||||
*out << " " << *span.name << ":" << span.firstChar << "," << span.lastChar;
|
||||
*out << " " << *span.name << ":" << span.first_char << ","
|
||||
<< span.last_char;
|
||||
}
|
||||
}
|
||||
|
||||
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
|
||||
|
||||
bool FileReference::equals(const Value* value) const {
|
||||
const FileReference* other = valueCast<FileReference>(value);
|
||||
bool FileReference::Equals(const Value* value) const {
|
||||
const FileReference* other = ValueCast<FileReference>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return *path == *other->path;
|
||||
}
|
||||
|
||||
bool FileReference::flatten(android::Res_value* outValue) const {
|
||||
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
bool FileReference::Flatten(android::Res_value* out_value) const {
|
||||
if (path.index() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue->dataType = android::Res_value::TYPE_STRING;
|
||||
outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex()));
|
||||
out_value->dataType = android::Res_value::TYPE_STRING;
|
||||
out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index()));
|
||||
return true;
|
||||
}
|
||||
|
||||
FileReference* FileReference::clone(StringPool* newPool) const {
|
||||
FileReference* fr = new FileReference(newPool->makeRef(*path));
|
||||
FileReference* FileReference::Clone(StringPool* new_pool) const {
|
||||
FileReference* fr = new FileReference(new_pool->MakeRef(*path));
|
||||
fr->file = file;
|
||||
fr->mComment = mComment;
|
||||
fr->mSource = mSource;
|
||||
fr->comment_ = comment_;
|
||||
fr->source_ = source_;
|
||||
return fr;
|
||||
}
|
||||
|
||||
void FileReference::print(std::ostream* out) const {
|
||||
void FileReference::Print(std::ostream* out) const {
|
||||
*out << "(file) " << *path;
|
||||
}
|
||||
|
||||
@@ -250,8 +250,8 @@ BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
|
||||
value.data = data;
|
||||
}
|
||||
|
||||
bool BinaryPrimitive::equals(const Value* value) const {
|
||||
const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
|
||||
bool BinaryPrimitive::Equals(const Value* value) const {
|
||||
const BinaryPrimitive* other = ValueCast<BinaryPrimitive>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
@@ -259,17 +259,17 @@ bool BinaryPrimitive::equals(const Value* value) const {
|
||||
this->value.data == other->value.data;
|
||||
}
|
||||
|
||||
bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
|
||||
outValue->dataType = value.dataType;
|
||||
outValue->data = util::hostToDevice32(value.data);
|
||||
bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
|
||||
out_value->dataType = value.dataType;
|
||||
out_value->data = util::HostToDevice32(value.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
|
||||
BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
|
||||
return new BinaryPrimitive(*this);
|
||||
}
|
||||
|
||||
void BinaryPrimitive::print(std::ostream* out) const {
|
||||
void BinaryPrimitive::Print(std::ostream* out) const {
|
||||
switch (value.dataType) {
|
||||
case android::Res_value::TYPE_NULL:
|
||||
*out << "(null)";
|
||||
@@ -297,10 +297,10 @@ void BinaryPrimitive::print(std::ostream* out) const {
|
||||
}
|
||||
|
||||
Attribute::Attribute(bool w, uint32_t t)
|
||||
: typeMask(t),
|
||||
minInt(std::numeric_limits<int32_t>::min()),
|
||||
maxInt(std::numeric_limits<int32_t>::max()) {
|
||||
mWeak = w;
|
||||
: type_mask(t),
|
||||
min_int(std::numeric_limits<int32_t>::min()),
|
||||
max_int(std::numeric_limits<int32_t>::max()) {
|
||||
weak_ = w;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -308,8 +308,8 @@ T* addPointer(T& val) {
|
||||
return &val;
|
||||
}
|
||||
|
||||
bool Attribute::equals(const Value* value) const {
|
||||
const Attribute* other = valueCast<Attribute>(value);
|
||||
bool Attribute::Equals(const Value* value) const {
|
||||
const Attribute* other = ValueCast<Attribute>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
@@ -318,46 +318,46 @@ bool Attribute::equals(const Value* value) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeMask != other->typeMask || minInt != other->minInt ||
|
||||
maxInt != other->maxInt) {
|
||||
if (type_mask != other->type_mask || min_int != other->min_int ||
|
||||
max_int != other->max_int) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<const Symbol*> sortedA;
|
||||
std::transform(symbols.begin(), symbols.end(), std::back_inserter(sortedA),
|
||||
std::vector<const Symbol*> sorted_a;
|
||||
std::transform(symbols.begin(), symbols.end(), std::back_inserter(sorted_a),
|
||||
addPointer<const Symbol>);
|
||||
std::sort(sortedA.begin(), sortedA.end(),
|
||||
std::sort(sorted_a.begin(), sorted_a.end(),
|
||||
[](const Symbol* a, const Symbol* b) -> bool {
|
||||
return a->symbol.name < b->symbol.name;
|
||||
});
|
||||
|
||||
std::vector<const Symbol*> sortedB;
|
||||
std::vector<const Symbol*> sorted_b;
|
||||
std::transform(other->symbols.begin(), other->symbols.end(),
|
||||
std::back_inserter(sortedB), addPointer<const Symbol>);
|
||||
std::sort(sortedB.begin(), sortedB.end(),
|
||||
std::back_inserter(sorted_b), addPointer<const Symbol>);
|
||||
std::sort(sorted_b.begin(), sorted_b.end(),
|
||||
[](const Symbol* a, const Symbol* b) -> bool {
|
||||
return a->symbol.name < b->symbol.name;
|
||||
});
|
||||
|
||||
return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
|
||||
return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
|
||||
[](const Symbol* a, const Symbol* b) -> bool {
|
||||
return a->symbol.equals(&b->symbol) &&
|
||||
return a->symbol.Equals(&b->symbol) &&
|
||||
a->value == b->value;
|
||||
});
|
||||
}
|
||||
|
||||
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
|
||||
Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
|
||||
return new Attribute(*this);
|
||||
}
|
||||
|
||||
void Attribute::printMask(std::ostream* out) const {
|
||||
if (typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
void Attribute::PrintMask(std::ostream* out) const {
|
||||
if (type_mask == android::ResTable_map::TYPE_ANY) {
|
||||
*out << "any";
|
||||
return;
|
||||
}
|
||||
|
||||
bool set = false;
|
||||
if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -366,7 +366,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "reference";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -375,7 +375,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "string";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -384,7 +384,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "integer";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -393,7 +393,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "boolean";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -402,7 +402,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "color";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -411,7 +411,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "float";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -420,7 +420,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "dimension";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -429,7 +429,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "fraction";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -438,7 +438,7 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
*out << "enum";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
|
||||
if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
|
||||
if (!set) {
|
||||
set = true;
|
||||
} else {
|
||||
@@ -448,96 +448,96 @@ void Attribute::printMask(std::ostream* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
void Attribute::print(std::ostream* out) const {
|
||||
void Attribute::Print(std::ostream* out) const {
|
||||
*out << "(attr) ";
|
||||
printMask(out);
|
||||
PrintMask(out);
|
||||
|
||||
if (!symbols.empty()) {
|
||||
*out << " [" << util::joiner(symbols, ", ") << "]";
|
||||
*out << " [" << util::Joiner(symbols, ", ") << "]";
|
||||
}
|
||||
|
||||
if (minInt != std::numeric_limits<int32_t>::min()) {
|
||||
*out << " min=" << minInt;
|
||||
if (min_int != std::numeric_limits<int32_t>::min()) {
|
||||
*out << " min=" << min_int;
|
||||
}
|
||||
|
||||
if (maxInt != std::numeric_limits<int32_t>::max()) {
|
||||
*out << " max=" << maxInt;
|
||||
if (max_int != std::numeric_limits<int32_t>::max()) {
|
||||
*out << " max=" << max_int;
|
||||
}
|
||||
|
||||
if (isWeak()) {
|
||||
if (IsWeak()) {
|
||||
*out << " [weak]";
|
||||
}
|
||||
}
|
||||
|
||||
static void buildAttributeMismatchMessage(DiagMessage* msg,
|
||||
static void BuildAttributeMismatchMessage(DiagMessage* msg,
|
||||
const Attribute* attr,
|
||||
const Item* value) {
|
||||
*msg << "expected";
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) {
|
||||
*msg << " boolean";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_COLOR) {
|
||||
*msg << " color";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) {
|
||||
*msg << " dimension";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_ENUM) {
|
||||
*msg << " enum";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) {
|
||||
*msg << " flags";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) {
|
||||
*msg << " float";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) {
|
||||
*msg << " fraction";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) {
|
||||
*msg << " integer";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) {
|
||||
*msg << " reference";
|
||||
}
|
||||
|
||||
if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
|
||||
if (attr->type_mask & android::ResTable_map::TYPE_STRING) {
|
||||
*msg << " string";
|
||||
}
|
||||
|
||||
*msg << " but got " << *value;
|
||||
}
|
||||
|
||||
bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
|
||||
bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const {
|
||||
android::Res_value val = {};
|
||||
item->flatten(&val);
|
||||
item->Flatten(&val);
|
||||
|
||||
// Always allow references.
|
||||
const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
|
||||
if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
|
||||
if (outMsg) {
|
||||
buildAttributeMismatchMessage(outMsg, this, item);
|
||||
const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
|
||||
if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) {
|
||||
if (out_msg) {
|
||||
BuildAttributeMismatchMessage(out_msg, this, item);
|
||||
}
|
||||
return false;
|
||||
|
||||
} else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
|
||||
} else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) &
|
||||
android::ResTable_map::TYPE_INTEGER) {
|
||||
if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
|
||||
if (outMsg) {
|
||||
*outMsg << *item << " is less than minimum integer " << minInt;
|
||||
if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) {
|
||||
if (out_msg) {
|
||||
*out_msg << *item << " is less than minimum integer " << min_int;
|
||||
}
|
||||
return false;
|
||||
} else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
|
||||
if (outMsg) {
|
||||
*outMsg << *item << " is greater than maximum integer " << maxInt;
|
||||
} else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) {
|
||||
if (out_msg) {
|
||||
*out_msg << *item << " is greater than maximum integer " << max_int;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -545,14 +545,14 @@ bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Style::equals(const Value* value) const {
|
||||
const Style* other = valueCast<Style>(value);
|
||||
bool Style::Equals(const Value* value) const {
|
||||
const Style* other = ValueCast<Style>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
if (bool(parent) != bool(other->parent) ||
|
||||
(parent && other->parent &&
|
||||
!parent.value().equals(&other->parent.value()))) {
|
||||
!parent.value().Equals(&other->parent.value()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -560,51 +560,51 @@ bool Style::equals(const Value* value) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<const Entry*> sortedA;
|
||||
std::transform(entries.begin(), entries.end(), std::back_inserter(sortedA),
|
||||
std::vector<const Entry*> sorted_a;
|
||||
std::transform(entries.begin(), entries.end(), std::back_inserter(sorted_a),
|
||||
addPointer<const Entry>);
|
||||
std::sort(sortedA.begin(), sortedA.end(),
|
||||
std::sort(sorted_a.begin(), sorted_a.end(),
|
||||
[](const Entry* a, const Entry* b) -> bool {
|
||||
return a->key.name < b->key.name;
|
||||
});
|
||||
|
||||
std::vector<const Entry*> sortedB;
|
||||
std::vector<const Entry*> sorted_b;
|
||||
std::transform(other->entries.begin(), other->entries.end(),
|
||||
std::back_inserter(sortedB), addPointer<const Entry>);
|
||||
std::sort(sortedB.begin(), sortedB.end(),
|
||||
std::back_inserter(sorted_b), addPointer<const Entry>);
|
||||
std::sort(sorted_b.begin(), sorted_b.end(),
|
||||
[](const Entry* a, const Entry* b) -> bool {
|
||||
return a->key.name < b->key.name;
|
||||
});
|
||||
|
||||
return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(),
|
||||
return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
|
||||
[](const Entry* a, const Entry* b) -> bool {
|
||||
return a->key.equals(&b->key) &&
|
||||
a->value->equals(b->value.get());
|
||||
return a->key.Equals(&b->key) &&
|
||||
a->value->Equals(b->value.get());
|
||||
});
|
||||
}
|
||||
|
||||
Style* Style::clone(StringPool* newPool) const {
|
||||
Style* Style::Clone(StringPool* new_pool) const {
|
||||
Style* style = new Style();
|
||||
style->parent = parent;
|
||||
style->parentInferred = parentInferred;
|
||||
style->mComment = mComment;
|
||||
style->mSource = mSource;
|
||||
style->parent_inferred = parent_inferred;
|
||||
style->comment_ = comment_;
|
||||
style->source_ = source_;
|
||||
for (auto& entry : entries) {
|
||||
style->entries.push_back(
|
||||
Entry{entry.key, std::unique_ptr<Item>(entry.value->clone(newPool))});
|
||||
Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))});
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void Style::print(std::ostream* out) const {
|
||||
void Style::Print(std::ostream* out) const {
|
||||
*out << "(style) ";
|
||||
if (parent && parent.value().name) {
|
||||
if (parent.value().privateReference) {
|
||||
if (parent.value().private_reference) {
|
||||
*out << "*";
|
||||
}
|
||||
*out << parent.value().name.value();
|
||||
}
|
||||
*out << " [" << util::joiner(entries, ", ") << "]";
|
||||
*out << " [" << util::Joiner(entries, ", ") << "]";
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out,
|
||||
@@ -617,12 +617,12 @@ static ::std::ostream& operator<<(::std::ostream& out,
|
||||
out << "???";
|
||||
}
|
||||
out << " = ";
|
||||
value.value->print(&out);
|
||||
value.value->Print(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Array::equals(const Value* value) const {
|
||||
const Array* other = valueCast<Array>(value);
|
||||
bool Array::Equals(const Value* value) const {
|
||||
const Array* other = ValueCast<Array>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
@@ -634,26 +634,26 @@ bool Array::equals(const Value* value) const {
|
||||
return std::equal(items.begin(), items.end(), other->items.begin(),
|
||||
[](const std::unique_ptr<Item>& a,
|
||||
const std::unique_ptr<Item>& b) -> bool {
|
||||
return a->equals(b.get());
|
||||
return a->Equals(b.get());
|
||||
});
|
||||
}
|
||||
|
||||
Array* Array::clone(StringPool* newPool) const {
|
||||
Array* Array::Clone(StringPool* new_pool) const {
|
||||
Array* array = new Array();
|
||||
array->mComment = mComment;
|
||||
array->mSource = mSource;
|
||||
array->comment_ = comment_;
|
||||
array->source_ = source_;
|
||||
for (auto& item : items) {
|
||||
array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
|
||||
array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
void Array::print(std::ostream* out) const {
|
||||
*out << "(array) [" << util::joiner(items, ", ") << "]";
|
||||
void Array::Print(std::ostream* out) const {
|
||||
*out << "(array) [" << util::Joiner(items, ", ") << "]";
|
||||
}
|
||||
|
||||
bool Plural::equals(const Value* value) const {
|
||||
const Plural* other = valueCast<Plural>(value);
|
||||
bool Plural::Equals(const Value* value) const {
|
||||
const Plural* other = ValueCast<Plural>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
@@ -668,24 +668,24 @@ bool Plural::equals(const Value* value) const {
|
||||
if (bool(a) != bool(b)) {
|
||||
return false;
|
||||
}
|
||||
return bool(a) == bool(b) || a->equals(b.get());
|
||||
return bool(a) == bool(b) || a->Equals(b.get());
|
||||
});
|
||||
}
|
||||
|
||||
Plural* Plural::clone(StringPool* newPool) const {
|
||||
Plural* Plural::Clone(StringPool* new_pool) const {
|
||||
Plural* p = new Plural();
|
||||
p->mComment = mComment;
|
||||
p->mSource = mSource;
|
||||
p->comment_ = comment_;
|
||||
p->source_ = source_;
|
||||
const size_t count = values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (values[i]) {
|
||||
p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
|
||||
p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool));
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void Plural::print(std::ostream* out) const {
|
||||
void Plural::Print(std::ostream* out) const {
|
||||
*out << "(plural)";
|
||||
if (values[Zero]) {
|
||||
*out << " zero=" << *values[Zero];
|
||||
@@ -713,8 +713,8 @@ static ::std::ostream& operator<<(::std::ostream& out,
|
||||
return out << *item;
|
||||
}
|
||||
|
||||
bool Styleable::equals(const Value* value) const {
|
||||
const Styleable* other = valueCast<Styleable>(value);
|
||||
bool Styleable::Equals(const Value* value) const {
|
||||
const Styleable* other = ValueCast<Styleable>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
@@ -725,21 +725,21 @@ bool Styleable::equals(const Value* value) const {
|
||||
|
||||
return std::equal(entries.begin(), entries.end(), other->entries.begin(),
|
||||
[](const Reference& a, const Reference& b) -> bool {
|
||||
return a.equals(&b);
|
||||
return a.Equals(&b);
|
||||
});
|
||||
}
|
||||
|
||||
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
|
||||
Styleable* Styleable::Clone(StringPool* /*new_pool*/) const {
|
||||
return new Styleable(*this);
|
||||
}
|
||||
|
||||
void Styleable::print(std::ostream* out) const {
|
||||
void Styleable::Print(std::ostream* out) const {
|
||||
*out << "(styleable) "
|
||||
<< " [" << util::joiner(entries, ", ") << "]";
|
||||
<< " [" << util::Joiner(entries, ", ") << "]";
|
||||
}
|
||||
|
||||
bool operator<(const Reference& a, const Reference& b) {
|
||||
int cmp = a.name.valueOrDefault({}).compare(b.name.valueOrDefault({}));
|
||||
int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({}));
|
||||
if (cmp != 0) return cmp < 0;
|
||||
return a.id < b.id;
|
||||
}
|
||||
@@ -758,7 +758,7 @@ struct NameOnlyComparator {
|
||||
}
|
||||
};
|
||||
|
||||
void Styleable::mergeWith(Styleable* other) {
|
||||
void Styleable::MergeWith(Styleable* other) {
|
||||
// Compare only names, because some References may already have their IDs
|
||||
// assigned
|
||||
// (framework IDs that don't change).
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
#ifndef AAPT_RESOURCE_VALUES_H
|
||||
#define AAPT_RESOURCE_VALUES_H
|
||||
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "Resource.h"
|
||||
#include "StringPool.h"
|
||||
#include "io/File.h"
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct RawValueVisitor;
|
||||
@@ -46,58 +47,59 @@ struct Value {
|
||||
* Whether this value is weak and can be overridden without
|
||||
* warning or error. Default is false.
|
||||
*/
|
||||
bool isWeak() const { return mWeak; }
|
||||
bool IsWeak() const { return weak_; }
|
||||
|
||||
void setWeak(bool val) { mWeak = val; }
|
||||
void SetWeak(bool val) { weak_ = 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; }
|
||||
void SetTranslateable(bool val) { translateable_ = val; }
|
||||
|
||||
// Default true.
|
||||
bool isTranslateable() const { return mTranslateable; }
|
||||
bool IsTranslateable() const { return translateable_; }
|
||||
|
||||
/**
|
||||
* Returns the source where this value was defined.
|
||||
*/
|
||||
const Source& getSource() const { return mSource; }
|
||||
const Source& GetSource() const { return source_; }
|
||||
|
||||
void setSource(const Source& source) { mSource = source; }
|
||||
void SetSource(const Source& source) { source_ = source; }
|
||||
|
||||
void setSource(Source&& source) { mSource = std::move(source); }
|
||||
void SetSource(Source&& source) { source_ = std::move(source); }
|
||||
|
||||
/**
|
||||
* Returns the comment that was associated with this resource.
|
||||
*/
|
||||
const std::string& getComment() const { return mComment; }
|
||||
const std::string& GetComment() const { return comment_; }
|
||||
|
||||
void setComment(const StringPiece& str) { mComment = str.toString(); }
|
||||
void SetComment(const StringPiece& str) { comment_ = str.ToString(); }
|
||||
|
||||
void setComment(std::string&& str) { mComment = std::move(str); }
|
||||
void SetComment(std::string&& str) { comment_ = 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;
|
||||
virtual void Accept(RawValueVisitor* visitor) = 0;
|
||||
|
||||
/**
|
||||
* Clone the value.
|
||||
* Clone the value. new_pool is the new StringPool that
|
||||
* any resources with strings should use when copying their string.
|
||||
*/
|
||||
virtual Value* clone(StringPool* newPool) const = 0;
|
||||
virtual Value* Clone(StringPool* new_pool) const = 0;
|
||||
|
||||
/**
|
||||
* Human readable printout of this value.
|
||||
*/
|
||||
virtual void print(std::ostream* out) const = 0;
|
||||
virtual void Print(std::ostream* out) const = 0;
|
||||
|
||||
protected:
|
||||
Source mSource;
|
||||
std::string mComment;
|
||||
bool mWeak = false;
|
||||
bool mTranslateable = true;
|
||||
Source source_;
|
||||
std::string comment_;
|
||||
bool weak_ = false;
|
||||
bool translateable_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -105,7 +107,7 @@ struct Value {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseValue : public Value {
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
void Accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -115,14 +117,14 @@ struct Item : public Value {
|
||||
/**
|
||||
* Clone the Item.
|
||||
*/
|
||||
virtual Item* clone(StringPool* newPool) const override = 0;
|
||||
virtual Item* Clone(StringPool* new_pool) 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;
|
||||
virtual bool Flatten(android::Res_value* out_value) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -130,7 +132,7 @@ struct Item : public Value {
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseItem : public Item {
|
||||
void accept(RawValueVisitor* visitor) override;
|
||||
void Accept(RawValueVisitor* visitor) override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,18 +151,18 @@ struct Reference : public BaseItem<Reference> {
|
||||
|
||||
Maybe<ResourceName> name;
|
||||
Maybe<ResourceId> id;
|
||||
Reference::Type referenceType;
|
||||
bool privateReference = false;
|
||||
Reference::Type reference_type;
|
||||
bool private_reference = 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(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* out_value) const override;
|
||||
Reference* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
bool operator<(const Reference&, const Reference&);
|
||||
@@ -170,11 +172,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() { weak_ = true; }
|
||||
bool Equals(const Value* value) const override;
|
||||
bool Flatten(android::Res_value* out) const override;
|
||||
Id* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -187,10 +189,10 @@ struct RawString : public BaseItem<RawString> {
|
||||
|
||||
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* out_value) const override;
|
||||
RawString* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct String : public BaseItem<String> {
|
||||
@@ -198,10 +200,10 @@ struct String : public BaseItem<String> {
|
||||
|
||||
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* out_value) const override;
|
||||
String* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct StyledString : public BaseItem<StyledString> {
|
||||
@@ -209,10 +211,10 @@ struct StyledString : public BaseItem<StyledString> {
|
||||
|
||||
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* out_value) const override;
|
||||
StyledString* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct FileReference : public BaseItem<FileReference> {
|
||||
@@ -226,10 +228,10 @@ struct FileReference : public BaseItem<FileReference> {
|
||||
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* out_value) const override;
|
||||
FileReference* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -242,10 +244,10 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
|
||||
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* out_value) const override;
|
||||
BinaryPrimitive* Clone(StringPool* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Attribute : public BaseValue<Attribute> {
|
||||
@@ -254,18 +256,18 @@ struct Attribute : public BaseValue<Attribute> {
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
uint32_t typeMask;
|
||||
int32_t minInt;
|
||||
int32_t maxInt;
|
||||
uint32_t type_mask;
|
||||
int32_t min_int;
|
||||
int32_t max_int;
|
||||
std::vector<Symbol> symbols;
|
||||
|
||||
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* new_pool) const override;
|
||||
void PrintMask(std::ostream* out) const;
|
||||
void Print(std::ostream* out) const override;
|
||||
bool Matches(const Item* item, DiagMessage* out_msg) const;
|
||||
};
|
||||
|
||||
struct Style : public BaseValue<Style> {
|
||||
@@ -280,21 +282,21 @@ struct Style : public BaseValue<Style> {
|
||||
* If set to true, the parent was auto inferred from the
|
||||
* style's name.
|
||||
*/
|
||||
bool parentInferred = false;
|
||||
bool parent_inferred = false;
|
||||
|
||||
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* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Array : public BaseValue<Array> {
|
||||
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* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Plural : public BaseValue<Plural> {
|
||||
@@ -302,25 +304,25 @@ struct Plural : public BaseValue<Plural> {
|
||||
|
||||
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* new_pool) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
struct Styleable : public BaseValue<Styleable> {
|
||||
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);
|
||||
value.Print(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,100 +15,101 @@
|
||||
*/
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceTypeTest, ParseResourceTypes) {
|
||||
const ResourceType* type = parseResourceType("anim");
|
||||
const ResourceType* type = ParseResourceType("anim");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnim);
|
||||
|
||||
type = parseResourceType("animator");
|
||||
type = ParseResourceType("animator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnimator);
|
||||
|
||||
type = parseResourceType("array");
|
||||
type = ParseResourceType("array");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kArray);
|
||||
|
||||
type = parseResourceType("attr");
|
||||
type = ParseResourceType("attr");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttr);
|
||||
|
||||
type = parseResourceType("^attr-private");
|
||||
type = ParseResourceType("^attr-private");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttrPrivate);
|
||||
|
||||
type = parseResourceType("bool");
|
||||
type = ParseResourceType("bool");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kBool);
|
||||
|
||||
type = parseResourceType("color");
|
||||
type = ParseResourceType("color");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kColor);
|
||||
|
||||
type = parseResourceType("dimen");
|
||||
type = ParseResourceType("dimen");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDimen);
|
||||
|
||||
type = parseResourceType("drawable");
|
||||
type = ParseResourceType("drawable");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDrawable);
|
||||
|
||||
type = parseResourceType("fraction");
|
||||
type = ParseResourceType("fraction");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kFraction);
|
||||
|
||||
type = parseResourceType("id");
|
||||
type = ParseResourceType("id");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kId);
|
||||
|
||||
type = parseResourceType("integer");
|
||||
type = ParseResourceType("integer");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInteger);
|
||||
|
||||
type = parseResourceType("interpolator");
|
||||
type = ParseResourceType("interpolator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInterpolator);
|
||||
|
||||
type = parseResourceType("layout");
|
||||
type = ParseResourceType("layout");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kLayout);
|
||||
|
||||
type = parseResourceType("menu");
|
||||
type = ParseResourceType("menu");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMenu);
|
||||
|
||||
type = parseResourceType("mipmap");
|
||||
type = ParseResourceType("mipmap");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMipmap);
|
||||
|
||||
type = parseResourceType("plurals");
|
||||
type = ParseResourceType("plurals");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kPlurals);
|
||||
|
||||
type = parseResourceType("raw");
|
||||
type = ParseResourceType("raw");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kRaw);
|
||||
|
||||
type = parseResourceType("string");
|
||||
type = ParseResourceType("string");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kString);
|
||||
|
||||
type = parseResourceType("style");
|
||||
type = ParseResourceType("style");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kStyle);
|
||||
|
||||
type = parseResourceType("transition");
|
||||
type = ParseResourceType("transition");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kTransition);
|
||||
|
||||
type = parseResourceType("xml");
|
||||
type = ParseResourceType("xml");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kXml);
|
||||
|
||||
type = parseResourceType("blahaha");
|
||||
type = ParseResourceType("blahaha");
|
||||
EXPECT_EQ(type, nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,17 +48,17 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
|
||||
{0x04ce, SDK_LOLLIPOP},
|
||||
};
|
||||
|
||||
static bool lessEntryId(const std::pair<uint16_t, size_t>& p,
|
||||
static bool less_entry_id(const std::pair<uint16_t, size_t>& p,
|
||||
uint16_t entryId) {
|
||||
return p.first < entryId;
|
||||
}
|
||||
|
||||
size_t findAttributeSdkLevel(const ResourceId& id) {
|
||||
if (id.packageId() != 0x01 && id.typeId() != 0x01) {
|
||||
size_t FindAttributeSdkLevel(const ResourceId& id) {
|
||||
if (id.package_id() != 0x01 && id.type_id() != 0x01) {
|
||||
return 0;
|
||||
}
|
||||
auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(),
|
||||
id.entryId(), lessEntryId);
|
||||
id.entry_id(), less_entry_id);
|
||||
if (iter == sAttrIdMap.end()) {
|
||||
return SDK_LOLLIPOP_MR1;
|
||||
}
|
||||
@@ -727,7 +727,7 @@ static const std::unordered_map<std::string, size_t> sAttrMap = {
|
||||
{"windowActivityTransitions", 21},
|
||||
{"colorEdgeEffect", 21}};
|
||||
|
||||
size_t findAttributeSdkLevel(const ResourceName& name) {
|
||||
size_t FindAttributeSdkLevel(const ResourceName& name) {
|
||||
if (name.package != "android" && name.type != ResourceType::kAttr) {
|
||||
return 0;
|
||||
}
|
||||
@@ -739,7 +739,7 @@ size_t findAttributeSdkLevel(const ResourceName& name) {
|
||||
return SDK_LOLLIPOP_MR1;
|
||||
}
|
||||
|
||||
std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion() {
|
||||
std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() {
|
||||
return std::make_pair(StringPiece(sDevelopmentSdkCodeName),
|
||||
sDevelopmentSdkLevel);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#ifndef AAPT_SDK_CONSTANTS_H
|
||||
#define AAPT_SDK_CONSTANTS_H
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
enum {
|
||||
@@ -47,9 +47,9 @@ enum {
|
||||
SDK_MARSHMALLOW = 23,
|
||||
};
|
||||
|
||||
size_t findAttributeSdkLevel(const ResourceId& id);
|
||||
size_t findAttributeSdkLevel(const ResourceName& name);
|
||||
std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion();
|
||||
size_t FindAttributeSdkLevel(const ResourceId& id);
|
||||
size_t FindAttributeSdkLevel(const ResourceName& name);
|
||||
std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion();
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
|
||||
@@ -16,23 +16,23 @@
|
||||
|
||||
#include "SdkConstants.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
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
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
#ifndef AAPT_SOURCE_H
|
||||
#define AAPT_SOURCE_H
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -36,13 +36,13 @@ struct Source {
|
||||
Source() = default;
|
||||
|
||||
inline Source(const StringPiece& path)
|
||||
: path(path.toString()) { // NOLINT(implicit)
|
||||
: path(path.ToString()) { // NOLINT(implicit)
|
||||
}
|
||||
|
||||
inline Source(const StringPiece& path, size_t line)
|
||||
: path(path.toString()), line(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); }
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -15,263 +15,266 @@
|
||||
*/
|
||||
|
||||
#include "StringPool.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
StringPool::Ref::Ref() : mEntry(nullptr) {}
|
||||
StringPool::Ref::Ref() : entry_(nullptr) {}
|
||||
|
||||
StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) {
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) {
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::~Ref() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
if (rhs.entry_ != nullptr) {
|
||||
rhs.entry_->ref_++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
entry_ = rhs.entry_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string* StringPool::Ref::operator->() const {
|
||||
return &mEntry->value;
|
||||
return &entry_->value;
|
||||
}
|
||||
|
||||
const std::string& StringPool::Ref::operator*() const { return mEntry->value; }
|
||||
const std::string& StringPool::Ref::operator*() const { return entry_->value; }
|
||||
|
||||
size_t StringPool::Ref::getIndex() const { return mEntry->index; }
|
||||
size_t StringPool::Ref::index() const { return entry_->index; }
|
||||
|
||||
const StringPool::Context& StringPool::Ref::getContext() const {
|
||||
return mEntry->context;
|
||||
const StringPool::Context& StringPool::Ref::GetContext() const {
|
||||
return entry_->context;
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef() : mEntry(nullptr) {}
|
||||
StringPool::StyleRef::StyleRef() : entry_(nullptr) {}
|
||||
|
||||
StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs)
|
||||
: mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
: entry_(rhs.entry_) {
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) {
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::~StyleRef() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef& StringPool::StyleRef::operator=(
|
||||
const StringPool::StyleRef& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
if (rhs.entry_ != nullptr) {
|
||||
rhs.entry_->ref_++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
if (entry_ != nullptr) {
|
||||
entry_->ref_--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
entry_ = rhs.entry_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
|
||||
return mEntry;
|
||||
return entry_;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
|
||||
return *mEntry;
|
||||
return *entry_;
|
||||
}
|
||||
|
||||
size_t StringPool::StyleRef::getIndex() const { return mEntry->str.getIndex(); }
|
||||
size_t StringPool::StyleRef::index() const { return entry_->str.index(); }
|
||||
|
||||
const StringPool::Context& StringPool::StyleRef::getContext() const {
|
||||
return mEntry->str.getContext();
|
||||
const StringPool::Context& StringPool::StyleRef::GetContext() const {
|
||||
return entry_->str.GetContext();
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece& str) {
|
||||
return makeRefImpl(str, Context{}, true);
|
||||
StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
|
||||
return MakeRefImpl(str, Context{}, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece& str,
|
||||
StringPool::Ref StringPool::MakeRef(const StringPiece& str,
|
||||
const Context& context) {
|
||||
return makeRefImpl(str, context, true);
|
||||
return MakeRefImpl(str, context, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRefImpl(const StringPiece& str,
|
||||
StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str,
|
||||
const Context& context, bool unique) {
|
||||
if (unique) {
|
||||
auto iter = mIndexedStrings.find(str);
|
||||
if (iter != std::end(mIndexedStrings)) {
|
||||
auto iter = indexed_strings_.find(str);
|
||||
if (iter != std::end(indexed_strings_)) {
|
||||
return Ref(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.toString();
|
||||
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));
|
||||
entry->index = strings_.size();
|
||||
entry->ref_ = 0;
|
||||
strings_.emplace_back(entry);
|
||||
indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
return Ref(entry);
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
|
||||
return makeRef(str, Context{});
|
||||
StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
|
||||
return MakeRef(str, Context{});
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str,
|
||||
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));
|
||||
entry->index = strings_.size();
|
||||
entry->ref_ = 0;
|
||||
strings_.emplace_back(entry);
|
||||
indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
|
||||
|
||||
StyleEntry* styleEntry = new StyleEntry();
|
||||
styleEntry->str = Ref(entry);
|
||||
StyleEntry* style_entry = new StyleEntry();
|
||||
style_entry->str = Ref(entry);
|
||||
for (const aapt::Span& span : str.spans) {
|
||||
styleEntry->spans.emplace_back(
|
||||
Span{makeRef(span.name), span.firstChar, span.lastChar});
|
||||
style_entry->spans.emplace_back(
|
||||
Span{MakeRef(span.name), span.first_char, span.last_char});
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
style_entry->ref_ = 0;
|
||||
styles_.emplace_back(style_entry);
|
||||
return StyleRef(style_entry);
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
|
||||
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->value = *ref.entry_->str;
|
||||
entry->context = ref.entry_->str.entry_->context;
|
||||
entry->index = strings_.size();
|
||||
entry->ref_ = 0;
|
||||
strings_.emplace_back(entry);
|
||||
indexed_strings_.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* style_entry = new StyleEntry();
|
||||
style_entry->str = Ref(entry);
|
||||
for (const Span& span : ref.entry_->spans) {
|
||||
style_entry->spans.emplace_back(
|
||||
Span{MakeRef(*span.name), span.first_char, span.last_char});
|
||||
}
|
||||
styleEntry->ref = 0;
|
||||
mStyles.emplace_back(styleEntry);
|
||||
return StyleRef(styleEntry);
|
||||
style_entry->ref_ = 0;
|
||||
styles_.emplace_back(style_entry);
|
||||
return StyleRef(style_entry);
|
||||
}
|
||||
|
||||
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();
|
||||
void StringPool::Merge(StringPool&& pool) {
|
||||
indexed_strings_.insert(pool.indexed_strings_.begin(),
|
||||
pool.indexed_strings_.end());
|
||||
pool.indexed_strings_.clear();
|
||||
std::move(pool.strings_.begin(), pool.strings_.end(),
|
||||
std::back_inserter(strings_));
|
||||
pool.strings_.clear();
|
||||
std::move(pool.styles_.begin(), pool.styles_.end(),
|
||||
std::back_inserter(styles_));
|
||||
pool.styles_.clear();
|
||||
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
const size_t len = strings_.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
strings_[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
|
||||
mStrings.reserve(mStrings.size() + stringCount);
|
||||
mStyles.reserve(mStyles.size() + styleCount);
|
||||
void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) {
|
||||
strings_.reserve(strings_.size() + stringCount);
|
||||
styles_.reserve(styles_.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);
|
||||
void StringPool::Prune() {
|
||||
const auto iter_end = indexed_strings_.end();
|
||||
auto index_iter = indexed_strings_.begin();
|
||||
while (index_iter != iter_end) {
|
||||
if (index_iter->second->ref_ <= 0) {
|
||||
index_iter = indexed_strings_.erase(index_iter);
|
||||
} else {
|
||||
++indexIter;
|
||||
++index_iter;
|
||||
}
|
||||
}
|
||||
|
||||
auto endIter2 =
|
||||
std::remove_if(std::begin(mStrings), std::end(mStrings),
|
||||
auto end_iter2 =
|
||||
std::remove_if(strings_.begin(), strings_.end(),
|
||||
[](const std::unique_ptr<Entry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
return entry->ref_ <= 0;
|
||||
});
|
||||
|
||||
auto endIter3 =
|
||||
std::remove_if(std::begin(mStyles), std::end(mStyles),
|
||||
auto end_iter3 =
|
||||
std::remove_if(styles_.begin(), styles_.end(),
|
||||
[](const std::unique_ptr<StyleEntry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
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));
|
||||
strings_.erase(end_iter2, strings_.end());
|
||||
styles_.erase(end_iter3, styles_.end());
|
||||
|
||||
// Reassign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
const size_t len = strings_.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
strings_[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::sort(
|
||||
void StringPool::Sort(
|
||||
const std::function<bool(const Entry&, const Entry&)>& cmp) {
|
||||
std::sort(
|
||||
std::begin(mStrings), std::end(mStrings),
|
||||
strings_.begin(), strings_.end(),
|
||||
[&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();
|
||||
const size_t len = strings_.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
strings_[index]->index = index;
|
||||
}
|
||||
|
||||
// Reorder the styles.
|
||||
std::sort(std::begin(mStyles), std::end(mStyles),
|
||||
std::sort(styles_.begin(), styles_.end(),
|
||||
[](const std::unique_ptr<StyleEntry>& lhs,
|
||||
const std::unique_ptr<StyleEntry>& rhs) -> bool {
|
||||
return lhs->str.getIndex() < rhs->str.getIndex();
|
||||
return lhs->str.index() < rhs->str.index();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* encodeLength(T* data, size_t length) {
|
||||
static T* EncodeLength(T* data, size_t length) {
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
@@ -284,7 +287,7 @@ static T* encodeLength(T* data, size_t length) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static size_t encodedLengthUnits(size_t length) {
|
||||
static size_t EncodedLengthUnits(size_t length) {
|
||||
static_assert(std::is_integral<T>::value, "wat.");
|
||||
|
||||
constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
|
||||
@@ -292,10 +295,10 @@ static size_t encodedLengthUnits(size_t length) {
|
||||
return length > kMaxSize ? 2 : 1;
|
||||
}
|
||||
|
||||
bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
|
||||
const size_t startIndex = out->size();
|
||||
bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
|
||||
const size_t start_index = out->size();
|
||||
android::ResStringPool_header* header =
|
||||
out->nextBlock<android::ResStringPool_header>();
|
||||
out->NextBlock<android::ResStringPool_header>();
|
||||
header->header.type = android::RES_STRING_POOL_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->stringCount = pool.size();
|
||||
@@ -304,92 +307,90 @@ bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
|
||||
}
|
||||
|
||||
uint32_t* indices =
|
||||
pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
|
||||
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);
|
||||
uint32_t* style_indices = nullptr;
|
||||
if (!pool.styles_.empty()) {
|
||||
header->styleCount = pool.styles_.back()->str.index() + 1;
|
||||
style_indices = out->NextBlock<uint32_t>(header->styleCount);
|
||||
}
|
||||
|
||||
const size_t beforeStringsIndex = out->size();
|
||||
header->stringsStart = beforeStringsIndex - startIndex;
|
||||
const size_t before_strings_index = out->size();
|
||||
header->stringsStart = before_strings_index - start_index;
|
||||
|
||||
for (const auto& entry : pool) {
|
||||
*indices = out->size() - beforeStringsIndex;
|
||||
*indices = out->size() - before_strings_index;
|
||||
indices++;
|
||||
|
||||
if (utf8) {
|
||||
const std::string& encoded = entry->value;
|
||||
const ssize_t utf16Length = utf8_to_utf16_length(
|
||||
const ssize_t utf16_length = utf8_to_utf16_length(
|
||||
reinterpret_cast<const uint8_t*>(entry->value.data()),
|
||||
entry->value.size());
|
||||
assert(utf16Length >= 0);
|
||||
CHECK(utf16_length >= 0);
|
||||
|
||||
const size_t totalSize = encodedLengthUnits<char>(utf16Length) +
|
||||
encodedLengthUnits<char>(encoded.length()) +
|
||||
encoded.size() + 1;
|
||||
const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
|
||||
EncodedLengthUnits<char>(encoded.length()) +
|
||||
encoded.size() + 1;
|
||||
|
||||
char* data = out->nextBlock<char>(totalSize);
|
||||
char* data = out->NextBlock<char>(total_size);
|
||||
|
||||
// First encode the UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
data = EncodeLength(data, utf16_length);
|
||||
|
||||
// Now encode the size of the real UTF8 string.
|
||||
data = encodeLength(data, encoded.length());
|
||||
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();
|
||||
const std::u16string encoded = util::Utf8ToUtf16(entry->value);
|
||||
const ssize_t utf16_length = encoded.size();
|
||||
|
||||
// Total number of 16-bit words to write.
|
||||
const size_t totalSize =
|
||||
encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1;
|
||||
const size_t total_size =
|
||||
EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
|
||||
|
||||
char16_t* data = out->nextBlock<char16_t>(totalSize);
|
||||
char16_t* data = out->NextBlock<char16_t>(total_size);
|
||||
|
||||
// Encode the actual UTF16 string length.
|
||||
data = encodeLength(data, utf16Length);
|
||||
const size_t byteLength = encoded.size() * sizeof(char16_t);
|
||||
data = EncodeLength(data, utf16_length);
|
||||
const size_t byte_length = 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);
|
||||
// entry->value.size()) truncates the string.
|
||||
memcpy(data, encoded.data(), byte_length);
|
||||
|
||||
// The null-terminating character is already here due to the block of data
|
||||
// being set
|
||||
// to 0s on allocation.
|
||||
// being set to 0s on allocation.
|
||||
}
|
||||
}
|
||||
|
||||
out->align4();
|
||||
out->Align4();
|
||||
|
||||
if (!pool.mStyles.empty()) {
|
||||
const size_t beforeStylesIndex = out->size();
|
||||
header->stylesStart = beforeStylesIndex - startIndex;
|
||||
if (!pool.styles_.empty()) {
|
||||
const size_t before_styles_index = out->size();
|
||||
header->stylesStart = before_styles_index - start_index;
|
||||
|
||||
size_t currentIndex = 0;
|
||||
for (const auto& entry : pool.mStyles) {
|
||||
while (entry->str.getIndex() > currentIndex) {
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
size_t current_index = 0;
|
||||
for (const auto& entry : pool.styles_) {
|
||||
while (entry->str.index() > current_index) {
|
||||
style_indices[current_index++] = out->size() - before_styles_index;
|
||||
|
||||
uint32_t* spanOffset = out->nextBlock<uint32_t>();
|
||||
*spanOffset = android::ResStringPool_span::END;
|
||||
uint32_t* span_offset = out->NextBlock<uint32_t>();
|
||||
*span_offset = android::ResStringPool_span::END;
|
||||
}
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
style_indices[current_index++] = out->size() - before_styles_index;
|
||||
|
||||
android::ResStringPool_span* span =
|
||||
out->nextBlock<android::ResStringPool_span>(entry->spans.size());
|
||||
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->name.index = s.name.index();
|
||||
span->firstChar = s.first_char;
|
||||
span->lastChar = s.last_char;
|
||||
span++;
|
||||
}
|
||||
|
||||
uint32_t* spanEnd = out->nextBlock<uint32_t>();
|
||||
uint32_t* spanEnd = out->NextBlock<uint32_t>();
|
||||
*spanEnd = android::ResStringPool_span::END;
|
||||
}
|
||||
|
||||
@@ -397,22 +398,22 @@ bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
|
||||
// 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();
|
||||
const size_t padding_length = sizeof(android::ResStringPool_span) -
|
||||
sizeof(android::ResStringPool_span::name);
|
||||
uint8_t* padding = out->NextBlock<uint8_t>(padding_length);
|
||||
memset(padding, 0xff, padding_length);
|
||||
out->Align4();
|
||||
}
|
||||
header->header.size = out->size() - startIndex;
|
||||
header->header.size = out->size() - start_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
|
||||
return flatten(out, pool, true);
|
||||
bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool) {
|
||||
return Flatten(out, pool, true);
|
||||
}
|
||||
|
||||
bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) {
|
||||
return flatten(out, pool, false);
|
||||
bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool) {
|
||||
return Flatten(out, pool, false);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,22 +17,22 @@
|
||||
#ifndef AAPT_STRING_POOL_H
|
||||
#define AAPT_STRING_POOL_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Span {
|
||||
std::string name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
uint32_t first_char;
|
||||
uint32_t last_char;
|
||||
};
|
||||
|
||||
struct StyleString {
|
||||
@@ -72,15 +72,15 @@ class StringPool {
|
||||
const std::string* operator->() const;
|
||||
const std::string& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
size_t index() const;
|
||||
const Context& GetContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
explicit Ref(Entry* entry);
|
||||
|
||||
Entry* mEntry;
|
||||
Entry* entry_;
|
||||
};
|
||||
|
||||
class StyleEntry;
|
||||
@@ -95,15 +95,15 @@ class StringPool {
|
||||
const StyleEntry* operator->() const;
|
||||
const StyleEntry& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
size_t index() const;
|
||||
const Context& GetContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
explicit StyleRef(StyleEntry* entry);
|
||||
|
||||
StyleEntry* mEntry;
|
||||
StyleEntry* entry_;
|
||||
};
|
||||
|
||||
class Entry {
|
||||
@@ -116,13 +116,13 @@ class StringPool {
|
||||
friend class StringPool;
|
||||
friend class Ref;
|
||||
|
||||
int ref;
|
||||
int ref_;
|
||||
};
|
||||
|
||||
struct Span {
|
||||
Ref name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
uint32_t first_char;
|
||||
uint32_t last_char;
|
||||
};
|
||||
|
||||
class StyleEntry {
|
||||
@@ -134,13 +134,13 @@ class StringPool {
|
||||
friend class StringPool;
|
||||
friend class StyleRef;
|
||||
|
||||
int ref;
|
||||
int ref_;
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -149,84 +149,84 @@ class StringPool {
|
||||
* Adds a string to the pool, unless it already exists. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece& str);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
void Merge(StringPool&& pool);
|
||||
|
||||
/**
|
||||
* Retuns the number of strings in the table.
|
||||
* Returns 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);
|
||||
void HintWillAdd(size_t string_count, size_t style_count);
|
||||
|
||||
/**
|
||||
* Sorts the strings according to some comparison function.
|
||||
*/
|
||||
void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
|
||||
void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
|
||||
|
||||
/**
|
||||
* Removes any strings that have no references.
|
||||
*/
|
||||
void prune();
|
||||
void Prune();
|
||||
|
||||
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>> strings_;
|
||||
std::vector<std::unique_ptr<StyleEntry>> styles_;
|
||||
std::unordered_multimap<StringPiece, Entry*> indexed_strings_;
|
||||
};
|
||||
|
||||
//
|
||||
// Inline implementation
|
||||
//
|
||||
|
||||
inline size_t StringPool::size() const { return mStrings.size(); }
|
||||
inline size_t StringPool::size() const { return strings_.size(); }
|
||||
|
||||
inline StringPool::const_iterator begin(const StringPool& pool) {
|
||||
return pool.mStrings.begin();
|
||||
return pool.strings_.begin();
|
||||
}
|
||||
|
||||
inline StringPool::const_iterator end(const StringPool& pool) {
|
||||
return pool.mStrings.end();
|
||||
return pool.strings_.end();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,25 +15,26 @@
|
||||
*/
|
||||
|
||||
#include "StringPool.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(StringPoolTest, InsertOneString) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref = pool.MakeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, InsertTwoUniqueStrings) {
|
||||
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");
|
||||
@@ -42,8 +43,8 @@ TEST(StringPoolTest, InsertTwoUniqueStrings) {
|
||||
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
|
||||
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");
|
||||
@@ -53,28 +54,28 @@ TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
|
||||
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
|
||||
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.index());
|
||||
EXPECT_EQ(1u, ref2.index());
|
||||
EXPECT_EQ(2u, ref3.index());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, PruneStringsWithNoReferences) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref refA = pool.makeRef("foo");
|
||||
StringPool::Ref refA = pool.MakeRef("foo");
|
||||
{
|
||||
StringPool::Ref ref = pool.makeRef("wut");
|
||||
StringPool::Ref ref = pool.MakeRef("wut");
|
||||
EXPECT_EQ(*ref, "wut");
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
}
|
||||
StringPool::Ref refB = pool.makeRef("bar");
|
||||
StringPool::Ref refB = pool.MakeRef("bar");
|
||||
|
||||
EXPECT_EQ(3u, pool.size());
|
||||
pool.prune();
|
||||
pool.Prune();
|
||||
EXPECT_EQ(2u, pool.size());
|
||||
StringPool::const_iterator iter = begin(pool);
|
||||
EXPECT_EQ((*iter)->value, "foo");
|
||||
@@ -87,51 +88,51 @@ TEST(StringPoolTest, PruneStringsWithNoReferences) {
|
||||
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
|
||||
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(0u, ref.index());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(1u, ref2.index());
|
||||
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(2u, ref3.index());
|
||||
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
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(2u, ref.index());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), "a");
|
||||
EXPECT_EQ(0u, ref2.getIndex());
|
||||
EXPECT_EQ(0u, ref2.index());
|
||||
|
||||
EXPECT_EQ(*ref3, "m");
|
||||
EXPECT_EQ(1u, ref3.getIndex());
|
||||
EXPECT_EQ(1u, ref3.index());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndStillDedupe) {
|
||||
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 {
|
||||
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.index(), ref.index());
|
||||
EXPECT_EQ(ref5.index(), ref2.index());
|
||||
EXPECT_EQ(ref6.index(), ref3.index());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, AddStyles) {
|
||||
@@ -139,27 +140,27 @@ TEST(StringPoolTest, AddStyles) {
|
||||
|
||||
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(0u, ref.index());
|
||||
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);
|
||||
EXPECT_EQ(2u, span.first_char);
|
||||
EXPECT_EQ(6u, span.last_char);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef("android");
|
||||
StringPool::Ref ref = pool.MakeRef("android");
|
||||
|
||||
StyleString str{{"android"}};
|
||||
StringPool::StyleRef styleRef = pool.makeRef(str);
|
||||
StringPool::StyleRef styleRef = pool.MakeRef(str);
|
||||
|
||||
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
|
||||
EXPECT_NE(ref.index(), styleRef.index());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
|
||||
@@ -167,9 +168,9 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
|
||||
|
||||
StringPool pool;
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf8(&buffer, pool);
|
||||
StringPool::FlattenUtf8(&buffer, pool);
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
|
||||
ResStringPool test;
|
||||
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
|
||||
}
|
||||
@@ -178,11 +179,11 @@ TEST(StringPoolTest, FlattenOddCharactersUtf16) {
|
||||
using namespace android; // For NO_ERROR on Windows.
|
||||
|
||||
StringPool pool;
|
||||
pool.makeRef("\u093f");
|
||||
pool.MakeRef("\u093f");
|
||||
BigBuffer buffer(1024);
|
||||
StringPool::flattenUtf16(&buffer, pool);
|
||||
StringPool::FlattenUtf16(&buffer, pool);
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
|
||||
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;
|
||||
@@ -204,58 +205,58 @@ TEST(StringPoolTest, Flatten) {
|
||||
|
||||
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(
|
||||
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.index());
|
||||
EXPECT_EQ(1u, ref2.index());
|
||||
EXPECT_EQ(2u, ref3.index());
|
||||
EXPECT_EQ(3u, ref4.index());
|
||||
EXPECT_EQ(4u, ref5.index());
|
||||
|
||||
BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
|
||||
StringPool::flattenUtf8(&buffers[0], pool);
|
||||
StringPool::flattenUtf16(&buffers[1], pool);
|
||||
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);
|
||||
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
|
||||
|
||||
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);
|
||||
|
||||
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(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(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++;
|
||||
|
||||
@@ -24,32 +24,31 @@ namespace aapt {
|
||||
|
||||
/**
|
||||
* Visits a value and invokes the appropriate method based on its type. Does not
|
||||
* traverse
|
||||
* into compound types. Use ValueVisitor for that.
|
||||
* traverse into compound types. Use ValueVisitor for that.
|
||||
*/
|
||||
struct RawValueVisitor {
|
||||
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) override { /* NOLINT */ \
|
||||
VisitSubValues(value); \
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,44 +58,43 @@ struct RawValueVisitor {
|
||||
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;
|
||||
// super class so the compiler knows we are trying to call them.
|
||||
using RawValueVisitor::Visit;
|
||||
|
||||
void visitSubValues(Attribute* attribute) {
|
||||
void VisitSubValues(Attribute* attribute) {
|
||||
for (Attribute::Symbol& symbol : attribute->symbols) {
|
||||
visit(&symbol.symbol);
|
||||
Visit(&symbol.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Style* style) {
|
||||
void VisitSubValues(Style* style) {
|
||||
if (style->parent) {
|
||||
visit(&style->parent.value());
|
||||
Visit(&style->parent.value());
|
||||
}
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
visit(&entry.key);
|
||||
entry.value->accept(this);
|
||||
Visit(&entry.key);
|
||||
entry.value->Accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Array* array) {
|
||||
void VisitSubValues(Array* array) {
|
||||
for (std::unique_ptr<Item>& item : array->items) {
|
||||
item->accept(this);
|
||||
item->Accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Plural* plural) {
|
||||
void VisitSubValues(Plural* plural) {
|
||||
for (std::unique_ptr<Item>& item : plural->values) {
|
||||
if (item) {
|
||||
item->accept(this);
|
||||
item->Accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitSubValues(Styleable* styleable) {
|
||||
void VisitSubValues(Styleable* styleable) {
|
||||
for (Reference& reference : styleable->entries) {
|
||||
visit(&reference);
|
||||
Visit(&reference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +112,7 @@ template <typename T>
|
||||
struct DynCastVisitor : public RawValueVisitor {
|
||||
T* value = nullptr;
|
||||
|
||||
void visit(T* v) override { value = v; }
|
||||
void Visit(T* v) override { value = v; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,12 +122,12 @@ template <>
|
||||
struct DynCastVisitor<Item> : public RawValueVisitor {
|
||||
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));
|
||||
const T* ValueCast(const Value* value) {
|
||||
return ValueCast<T>(const_cast<Value*>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,30 +135,30 @@ const T* valueCast(const Value* value) {
|
||||
* Otherwise, returns nullptr.
|
||||
*/
|
||||
template <typename T>
|
||||
T* valueCast(Value* value) {
|
||||
T* ValueCast(Value* value) {
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
DynCastVisitor<T> visitor;
|
||||
value->accept(&visitor);
|
||||
value->Accept(&visitor);
|
||||
return visitor.value;
|
||||
}
|
||||
|
||||
inline void visitAllValuesInPackage(ResourceTablePackage* pkg,
|
||||
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);
|
||||
for (auto& config_value : entry->values) {
|
||||
config_value->value->Accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void visitAllValuesInTable(ResourceTable* table,
|
||||
inline void VisitAllValuesInTable(ResourceTable* table,
|
||||
RawValueVisitor* visitor) {
|
||||
for (auto& pkg : table->packages) {
|
||||
visitAllValuesInPackage(pkg.get(), visitor);
|
||||
VisitAllValuesInPackage(pkg.get(), visitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,40 +15,41 @@
|
||||
*/
|
||||
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ResourceValues.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SingleReferenceVisitor : public ValueVisitor {
|
||||
using ValueVisitor::visit;
|
||||
using ValueVisitor::Visit;
|
||||
|
||||
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*> visited_refs;
|
||||
Style* visited_style = nullptr;
|
||||
|
||||
void visit(Reference* ref) override { visitedRefs.push_back(ref); }
|
||||
void Visit(Reference* ref) override { visited_refs.push_back(ref); }
|
||||
|
||||
void visit(Style* style) override {
|
||||
visitedStyle = style;
|
||||
ValueVisitor::visit(style);
|
||||
void Visit(Style* style) override {
|
||||
visited_style = style;
|
||||
ValueVisitor::Visit(style);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ValueVisitorTest, VisitsReference) {
|
||||
Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"});
|
||||
SingleReferenceVisitor visitor;
|
||||
ref.accept(&visitor);
|
||||
ref.Accept(&visitor);
|
||||
|
||||
EXPECT_EQ(visitor.visited, &ref);
|
||||
}
|
||||
@@ -56,31 +57,31 @@ TEST(ValueVisitorTest, VisitsReference) {
|
||||
TEST(ValueVisitorTest, VisitsReferencesInStyle) {
|
||||
std::unique_ptr<Style> style =
|
||||
test::StyleBuilder()
|
||||
.setParent("android:style/foo")
|
||||
.addItem("android:attr/one", test::buildReference("android:id/foo"))
|
||||
.build();
|
||||
.SetParent("android:style/foo")
|
||||
.AddItem("android:attr/one", test::BuildReference("android:id/foo"))
|
||||
.Build();
|
||||
|
||||
StyleVisitor visitor;
|
||||
style->accept(&visitor);
|
||||
style->Accept(&visitor);
|
||||
|
||||
ASSERT_EQ(style.get(), visitor.visitedStyle);
|
||||
ASSERT_EQ(style.get(), visitor.visited_style);
|
||||
|
||||
// Entry attribute references, plus the parent reference, plus one value
|
||||
// reference.
|
||||
ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size());
|
||||
ASSERT_EQ(style->entries.size() + 2, visitor.visited_refs.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);
|
||||
.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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,13 +15,15 @@
|
||||
*/
|
||||
|
||||
#include "compile/IdAssigner.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -29,44 +31,44 @@ namespace aapt {
|
||||
* ResourceEntry,
|
||||
* as long as there is no existing ID or the ID is the same.
|
||||
*/
|
||||
static bool assignId(IDiagnostics* diag, const ResourceId& id,
|
||||
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 (pkg->id.value() == id.package_id()) {
|
||||
if (!type->id || type->id.value() == id.type_id()) {
|
||||
type->id = id.type_id();
|
||||
|
||||
if (!entry->id || entry->id.value() == id.entryId()) {
|
||||
entry->id = id.entryId();
|
||||
if (!entry->id || entry->id.value() == id.entry_id()) {
|
||||
entry->id = id.entry_id();
|
||||
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);
|
||||
const ResourceId existing_id(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 " << existing_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
std::map<ResourceId, ResourceName> assignedIds;
|
||||
bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
|
||||
std::map<ResourceId, ResourceName> assigned_ids;
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
CHECK(bool(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);
|
||||
|
||||
if (mAssignedIdMap) {
|
||||
if (assigned_id_map_) {
|
||||
// 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 auto iter = assigned_id_map_->find(name);
|
||||
if (iter != assigned_id_map_->end()) {
|
||||
const ResourceId assigned_id = iter->second;
|
||||
const bool result =
|
||||
assignId(context->getDiagnostics(), assignedId, name,
|
||||
AssignId(context->GetDiagnostics(), assigned_id, name,
|
||||
package.get(), type.get(), entry.get());
|
||||
if (!result) {
|
||||
return false;
|
||||
@@ -76,14 +78,14 @@ bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
|
||||
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;
|
||||
ResourceId resource_id(package->id.value(), type->id.value(),
|
||||
entry->id.value());
|
||||
auto result = assigned_ids.insert({resource_id, name});
|
||||
const ResourceName& existing_name = result.first->second;
|
||||
if (!result.second) {
|
||||
context->getDiagnostics()->error(
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "resource " << name << " has same ID "
|
||||
<< resourceId << " as " << existingName);
|
||||
<< resource_id << " as " << existing_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -91,20 +93,20 @@ bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
}
|
||||
}
|
||||
|
||||
if (mAssignedIdMap) {
|
||||
if (assigned_id_map_) {
|
||||
// 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);
|
||||
for (const auto& stable_id_entry : *assigned_id_map_) {
|
||||
const ResourceName& pre_assigned_name = stable_id_entry.first;
|
||||
const ResourceId& pre_assigned_id = stable_id_entry.second;
|
||||
auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name});
|
||||
const ResourceName& existing_name = result.first->second;
|
||||
if (!result.second && existing_name != pre_assigned_name) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "stable ID " << pre_assigned_id << " for resource "
|
||||
<< pre_assigned_name
|
||||
<< " is already taken by resource " << existing_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -114,21 +116,21 @@ bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// if possible,
|
||||
// unless those IDs have been reserved.
|
||||
|
||||
const auto assignedIdsIterEnd = assignedIds.end();
|
||||
const auto assigned_ids_iter_end = assigned_ids.end();
|
||||
for (auto& package : table->packages) {
|
||||
assert(package->id && "packages must have manually assigned IDs");
|
||||
CHECK(bool(package->id)) << "packages must have manually assigned IDs";
|
||||
|
||||
// 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;
|
||||
ResourceId resource_id(package->id.value(), 0, 0);
|
||||
uint8_t next_expected_type_id = 1;
|
||||
|
||||
// Find the closest matching ResourceId that is <= the one with only the
|
||||
// package set.
|
||||
auto nextTypeIter = assignedIds.lower_bound(resourceId);
|
||||
auto next_type_iter = assigned_ids.lower_bound(resource_id);
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
// We need to assign a type ID. Iterate over the reserved IDs until we
|
||||
@@ -136,41 +138,41 @@ bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// 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()) {
|
||||
while (next_type_iter != assigned_ids_iter_end) {
|
||||
if (next_type_iter->first.package_id() != package->id.value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t typeId = nextTypeIter->first.typeId();
|
||||
if (typeId > nextExpectedTypeId) {
|
||||
const uint8_t type_id = next_type_iter->first.type_id();
|
||||
if (type_id > next_expected_type_id) {
|
||||
// There is a gap in the type IDs, so use the missing one.
|
||||
type->id = nextExpectedTypeId++;
|
||||
type->id = next_expected_type_id++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set our expectation to be the next type ID after the reserved one
|
||||
// we
|
||||
// just saw.
|
||||
nextExpectedTypeId = typeId + 1;
|
||||
next_expected_type_id = type_id + 1;
|
||||
|
||||
// Move to the next reserved ID.
|
||||
++nextTypeIter;
|
||||
++next_type_iter;
|
||||
}
|
||||
|
||||
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++;
|
||||
type->id = next_expected_type_id++;
|
||||
}
|
||||
}
|
||||
|
||||
resourceId = ResourceId(package->id.value(), type->id.value(), 0);
|
||||
uint16_t nextExpectedEntryId = 0;
|
||||
resource_id = ResourceId(package->id.value(), type->id.value(), 0);
|
||||
uint16_t next_expected_entry_id = 0;
|
||||
|
||||
// Find the closest matching ResourceId that is <= the one with only the
|
||||
// package
|
||||
// and type set.
|
||||
auto nextEntryIter = assignedIds.lower_bound(resourceId);
|
||||
auto next_entry_iter = assigned_ids.lower_bound(resource_id);
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
// We need to assign an entry ID. Iterate over the reserved IDs until
|
||||
@@ -179,32 +181,32 @@ bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// 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()) {
|
||||
while (next_entry_iter != assigned_ids_iter_end) {
|
||||
if (next_entry_iter->first.package_id() != package->id.value() ||
|
||||
next_entry_iter->first.type_id() != type->id.value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint16_t entryId = nextEntryIter->first.entryId();
|
||||
if (entryId > nextExpectedEntryId) {
|
||||
const uint16_t entry_id = next_entry_iter->first.entry_id();
|
||||
if (entry_id > next_expected_entry_id) {
|
||||
// There is a gap in the entry IDs, so use the missing one.
|
||||
entry->id = nextExpectedEntryId++;
|
||||
entry->id = next_expected_entry_id++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set our expectation to be the next type ID after the reserved one
|
||||
// we
|
||||
// just saw.
|
||||
nextExpectedEntryId = entryId + 1;
|
||||
next_expected_entry_id = entry_id + 1;
|
||||
|
||||
// Move to the next reserved entry ID.
|
||||
++nextEntryIter;
|
||||
++next_entry_iter;
|
||||
}
|
||||
|
||||
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++;
|
||||
entry->id = next_expected_entry_id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
#ifndef AAPT_COMPILE_IDASSIGNER_H
|
||||
#define AAPT_COMPILE_IDASSIGNER_H
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Resource.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <unordered_map>
|
||||
#include "android-base/macros.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
@@ -34,12 +35,13 @@ class IdAssigner : public IResourceTableConsumer {
|
||||
public:
|
||||
IdAssigner() = default;
|
||||
explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map)
|
||||
: mAssignedIdMap(map) {}
|
||||
: assigned_id_map_(map) {}
|
||||
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool Consume(IAaptContext* context, ResourceTable* table) override;
|
||||
|
||||
private:
|
||||
const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
|
||||
const std::unordered_map<ResourceName, ResourceId>* assigned_id_map_ =
|
||||
nullptr;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,128 +15,129 @@
|
||||
*/
|
||||
|
||||
#include "compile/IdAssigner.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table);
|
||||
::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();
|
||||
.AddSimple("android:attr/foo")
|
||||
.AddSimple("android:attr/bar")
|
||||
.AddSimple("android:id/foo")
|
||||
.SetPackageId("android", 0x01)
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
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();
|
||||
.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();
|
||||
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> maybe_result;
|
||||
|
||||
// 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);
|
||||
maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two"));
|
||||
AAPT_ASSERT_TRUE(maybe_result);
|
||||
EXPECT_EQ(make_value<uint8_t>(2), maybe_result.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);
|
||||
maybe_result =
|
||||
table->FindResource(test::ParseNameOrDie("android:integer/three"));
|
||||
AAPT_ASSERT_TRUE(maybe_result);
|
||||
EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id);
|
||||
|
||||
// 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);
|
||||
maybe_result =
|
||||
table->FindResource(test::ParseNameOrDie("android:string/five"));
|
||||
AAPT_ASSERT_TRUE(maybe_result);
|
||||
EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id);
|
||||
|
||||
// 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);
|
||||
maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar"));
|
||||
AAPT_ASSERT_TRUE(maybe_result);
|
||||
EXPECT_EQ(make_value<uint16_t>(1), maybe_result.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);
|
||||
maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz"));
|
||||
AAPT_ASSERT_TRUE(maybe_result);
|
||||
EXPECT_EQ(make_value<uint16_t>(2), maybe_result.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();
|
||||
.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();
|
||||
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();
|
||||
.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()));
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
std::unordered_map<ResourceName, ResourceId> id_map = {
|
||||
{test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
|
||||
IdAssigner assigner(&id_map);
|
||||
ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
|
||||
ASSERT_TRUE(VerifyIds(table.get()));
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
table->findResource(test::parseNameOrDie("android:attr/foo"));
|
||||
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& search_result = result.value();
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id);
|
||||
EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id);
|
||||
EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.entry->id);
|
||||
}
|
||||
|
||||
::testing::AssertionResult verifyIds(ResourceTable* table) {
|
||||
std::set<uint8_t> packageIds;
|
||||
::testing::AssertionResult VerifyIds(ResourceTable* table) {
|
||||
std::set<uint8_t> package_ids;
|
||||
for (auto& package : table->packages) {
|
||||
if (!package->id) {
|
||||
return ::testing::AssertionFailure() << "package " << package->name
|
||||
<< " has no ID";
|
||||
}
|
||||
|
||||
if (!packageIds.insert(package->id.value()).second) {
|
||||
if (!package_ids.insert(package->id.value()).second) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "package " << package->name << " has non-unique ID " << std::hex
|
||||
<< (int)package->id.value() << std::dec;
|
||||
@@ -144,7 +145,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) {
|
||||
}
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::set<uint8_t> typeIds;
|
||||
std::set<uint8_t> type_ids;
|
||||
for (auto& type : package->types) {
|
||||
if (!type->id) {
|
||||
return ::testing::AssertionFailure() << "type " << type->type
|
||||
@@ -152,7 +153,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) {
|
||||
<< " has no ID";
|
||||
}
|
||||
|
||||
if (!typeIds.insert(type->id.value()).second) {
|
||||
if (!type_ids.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()
|
||||
@@ -161,7 +162,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) {
|
||||
}
|
||||
|
||||
for (auto& type : package->types) {
|
||||
std::set<uint16_t> entryIds;
|
||||
std::set<uint16_t> entry_ids;
|
||||
for (auto& entry : type->entries) {
|
||||
if (!entry->id) {
|
||||
return ::testing::AssertionFailure()
|
||||
@@ -169,7 +170,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) {
|
||||
<< " of package " << package->name << " has no ID";
|
||||
}
|
||||
|
||||
if (!entryIds.insert(entry->id.value()).second) {
|
||||
if (!entry_ids.insert(entry->id.value()).second) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "entry " << entry->name << " of type " << type->type
|
||||
<< " of package " << package->name << " has non-unique ID "
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
#ifndef AAPT_COMPILE_IMAGE_H
|
||||
#define AAPT_COMPILE_IMAGE_H
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -113,15 +114,15 @@ inline bool operator==(const Bounds& left, const Bounds& right) {
|
||||
*/
|
||||
class NinePatch {
|
||||
public:
|
||||
static std::unique_ptr<NinePatch> create(uint8_t** rows, const int32_t width,
|
||||
static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
|
||||
const int32_t height,
|
||||
std::string* errOut);
|
||||
std::string* err_out);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
static uint32_t PackRGBA(const uint8_t* pixel);
|
||||
|
||||
/**
|
||||
* 9-patch content padding/insets. All positions are relative to the 9-patch
|
||||
@@ -136,7 +137,7 @@ class NinePatch {
|
||||
* See
|
||||
* https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
|
||||
*/
|
||||
Bounds layoutBounds;
|
||||
Bounds layout_bounds;
|
||||
|
||||
/**
|
||||
* Outline of the image, calculated based on opacity.
|
||||
@@ -147,51 +148,51 @@ class NinePatch {
|
||||
* The computed radius of the outline. If non-zero, the outline is a
|
||||
* rounded-rect.
|
||||
*/
|
||||
float outlineRadius = 0.0f;
|
||||
float outline_radius = 0.0f;
|
||||
|
||||
/**
|
||||
* The largest alpha value within the outline.
|
||||
*/
|
||||
uint32_t outlineAlpha = 0x000000ffu;
|
||||
uint32_t outline_alpha = 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;
|
||||
std::vector<Range> horizontal_stretch_regions;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
std::vector<Range> vertical_stretch_regions;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
std::vector<uint32_t> region_colors;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
* separately using SerializeOpticalLayoutBounds() and
|
||||
* SerializeRoundedRectOutline().
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
|
||||
std::unique_ptr<uint8_t[]> SerializeBase(size_t* out_len) const;
|
||||
|
||||
/**
|
||||
* Serializes the layout bounds.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
|
||||
std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const;
|
||||
|
||||
/**
|
||||
* Serializes the rounded-rect outline.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
|
||||
std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const;
|
||||
|
||||
private:
|
||||
explicit NinePatch() = default;
|
||||
@@ -201,7 +202,7 @@ class 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);
|
||||
::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
*/
|
||||
|
||||
#include "compile/InlineXmlFormatParser.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Debug.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "util/Util.h"
|
||||
#include "xml/XmlDom.h"
|
||||
#include "xml/XmlUtil.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
@@ -34,38 +36,38 @@ namespace {
|
||||
*/
|
||||
class Visitor : public xml::PackageAwareVisitor {
|
||||
public:
|
||||
using xml::PackageAwareVisitor::visit;
|
||||
using xml::PackageAwareVisitor::Visit;
|
||||
|
||||
struct InlineDeclaration {
|
||||
xml::Element* el;
|
||||
std::string attrNamespaceUri;
|
||||
std::string attrName;
|
||||
std::string attr_namespace_uri;
|
||||
std::string attr_name;
|
||||
};
|
||||
|
||||
explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource)
|
||||
: mContext(context), mXmlResource(xmlResource) {}
|
||||
explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
|
||||
: context_(context), xml_resource_(xml_resource) {}
|
||||
|
||||
void visit(xml::Element* el) override {
|
||||
if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
|
||||
xml::PackageAwareVisitor::visit(el);
|
||||
void Visit(xml::Element* el) override {
|
||||
if (el->namespace_uri != xml::kSchemaAapt || el->name != "attr") {
|
||||
xml::PackageAwareVisitor::Visit(el);
|
||||
return;
|
||||
}
|
||||
|
||||
const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
|
||||
const Source& src = xml_resource_->file.source.WithLine(el->line_number);
|
||||
|
||||
xml::Attribute* attr = el->findAttribute({}, "name");
|
||||
xml::Attribute* attr = el->FindAttribute({}, "name");
|
||||
if (!attr) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(src)
|
||||
context_->GetDiagnostics()->Error(DiagMessage(src)
|
||||
<< "missing 'name' attribute");
|
||||
mError = true;
|
||||
error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
|
||||
Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
|
||||
if (!ref) {
|
||||
mContext->getDiagnostics()->error(
|
||||
context_->GetDiagnostics()->Error(
|
||||
DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
|
||||
mError = true;
|
||||
error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,63 +78,63 @@ class Visitor : public xml::PackageAwareVisitor {
|
||||
// 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)
|
||||
Maybe<xml::ExtractedPackage> maybe_pkg =
|
||||
TransformPackageAlias(name.package, {});
|
||||
if (!maybe_pkg) {
|
||||
context_->GetDiagnostics()->Error(DiagMessage(src)
|
||||
<< "invalid namespace prefix '"
|
||||
<< name.package << "'");
|
||||
mError = true;
|
||||
error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const xml::ExtractedPackage& pkg = maybePkg.value();
|
||||
const bool privateNamespace =
|
||||
pkg.privateNamespace || ref.value().privateReference;
|
||||
const xml::ExtractedPackage& pkg = maybe_pkg.value();
|
||||
const bool private_namespace =
|
||||
pkg.private_namespace || ref.value().private_reference;
|
||||
|
||||
InlineDeclaration decl;
|
||||
decl.el = el;
|
||||
decl.attrName = name.entry;
|
||||
decl.attr_name = name.entry;
|
||||
if (!pkg.package.empty()) {
|
||||
decl.attrNamespaceUri =
|
||||
xml::buildPackageNamespace(pkg.package, privateNamespace);
|
||||
decl.attr_namespace_uri =
|
||||
xml::BuildPackageNamespace(pkg.package, private_namespace);
|
||||
}
|
||||
|
||||
mInlineDeclarations.push_back(std::move(decl));
|
||||
inline_declarations_.push_back(std::move(decl));
|
||||
}
|
||||
|
||||
const std::vector<InlineDeclaration>& getInlineDeclarations() const {
|
||||
return mInlineDeclarations;
|
||||
const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
|
||||
return inline_declarations_;
|
||||
}
|
||||
|
||||
bool hasError() const { return mError; }
|
||||
bool HasError() const { return error_; }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Visitor);
|
||||
|
||||
IAaptContext* mContext;
|
||||
xml::XmlResource* mXmlResource;
|
||||
std::vector<InlineDeclaration> mInlineDeclarations;
|
||||
bool mError = false;
|
||||
IAaptContext* context_;
|
||||
xml::XmlResource* xml_resource_;
|
||||
std::vector<InlineDeclaration> inline_declarations_;
|
||||
bool error_ = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool InlineXmlFormatParser::consume(IAaptContext* context,
|
||||
bool InlineXmlFormatParser::Consume(IAaptContext* context,
|
||||
xml::XmlResource* doc) {
|
||||
Visitor visitor(context, doc);
|
||||
doc->root->accept(&visitor);
|
||||
if (visitor.hasError()) {
|
||||
doc->root->Accept(&visitor);
|
||||
if (visitor.HasError()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t nameSuffixCounter = 0;
|
||||
size_t name_suffix_counter = 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;
|
||||
visitor.GetInlineDeclarations()) {
|
||||
auto new_doc = util::make_unique<xml::XmlResource>();
|
||||
new_doc->file.config = doc->file.config;
|
||||
new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
|
||||
new_doc->file.name = doc->file.name;
|
||||
|
||||
// Modify the new entry name. We need to suffix the entry with a number to
|
||||
// avoid
|
||||
@@ -140,63 +142,64 @@ bool InlineXmlFormatParser::consume(IAaptContext* context,
|
||||
// won't show up
|
||||
// in R.java.
|
||||
|
||||
newDoc->file.name.entry = NameMangler::mangleEntry(
|
||||
{}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
|
||||
new_doc->file.name.entry =
|
||||
NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" +
|
||||
std::to_string(name_suffix_counter));
|
||||
|
||||
// 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)
|
||||
const Source child_source = doc->file.source.WithLine(child->line_number);
|
||||
if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
|
||||
if (!util::TrimWhitespace(t->text).empty()) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(child_source)
|
||||
<< "can't extract text into its own resource");
|
||||
return false;
|
||||
}
|
||||
} else if (newDoc->root) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(childSource)
|
||||
} else if (new_doc->root) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(child_source)
|
||||
<< "inline XML resources must have a single root");
|
||||
return false;
|
||||
} else {
|
||||
newDoc->root = std::move(child);
|
||||
newDoc->root->parent = nullptr;
|
||||
new_doc->root = std::move(child);
|
||||
new_doc->root->parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk up and find the parent element.
|
||||
xml::Node* node = decl.el;
|
||||
xml::Element* parentEl = nullptr;
|
||||
xml::Element* parent_el = nullptr;
|
||||
while (node->parent &&
|
||||
(parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
|
||||
(parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) {
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
if (!parentEl) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(newDoc->file.source)
|
||||
if (!parent_el) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(new_doc->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()});
|
||||
parent_el->attributes.push_back(
|
||||
xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
|
||||
"@" + new_doc->file.name.ToString()});
|
||||
|
||||
// Delete the subtree.
|
||||
for (auto iter = parentEl->children.begin();
|
||||
iter != parentEl->children.end(); ++iter) {
|
||||
for (auto iter = parent_el->children.begin();
|
||||
iter != parent_el->children.end(); ++iter) {
|
||||
if (iter->get() == node) {
|
||||
parentEl->children.erase(iter);
|
||||
parent_el->children.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mQueue.push_back(std::move(newDoc));
|
||||
queue_.push_back(std::move(new_doc));
|
||||
|
||||
nameSuffixCounter++;
|
||||
name_suffix_counter++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
#ifndef AAPT_COMPILE_INLINEXMLFORMATPARSER_H
|
||||
#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -50,17 +51,17 @@ class InlineXmlFormatParser : public IXmlResourceConsumer {
|
||||
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;
|
||||
GetExtractedInlineXmlDocuments() {
|
||||
return queue_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
|
||||
|
||||
std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
|
||||
std::vector<std::unique_ptr<xml::XmlResource>> queue_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
#include "compile/InlineXmlFormatParser.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
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" />
|
||||
@@ -29,13 +30,13 @@ TEST(InlineXmlFormatParserTest, PassThrough) {
|
||||
</View>)EOF");
|
||||
|
||||
InlineXmlFormatParser parser;
|
||||
ASSERT_TRUE(parser.consume(context.get(), doc.get()));
|
||||
EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
|
||||
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,48 +46,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()));
|
||||
ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
|
||||
|
||||
// One XML resource should have been extracted.
|
||||
EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
|
||||
EXPECT_EQ(1u, parser.GetExtractedInlineXmlDocuments().size());
|
||||
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
xml::Element* el = xml::FindRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
EXPECT_EQ("View1", el->name);
|
||||
|
||||
// The <aapt:attr> tag should be extracted.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
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");
|
||||
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
|
||||
ResourceNameRef nameRef;
|
||||
ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
|
||||
ResourceNameRef name_ref;
|
||||
ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref));
|
||||
|
||||
xml::XmlResource* extractedDoc =
|
||||
parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDoc);
|
||||
xml::XmlResource* extracted_doc =
|
||||
parser.GetExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extracted_doc);
|
||||
|
||||
// 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);
|
||||
EXPECT_EQ(name_ref.package, extracted_doc->file.name.package);
|
||||
EXPECT_EQ(name_ref.type, extracted_doc->file.name.type);
|
||||
EXPECT_EQ(name_ref.entry, extracted_doc->file.name.entry);
|
||||
|
||||
// Verify the structure of the extracted XML.
|
||||
el = xml::findRootElement(extractedDoc);
|
||||
el = xml::FindRootElement(extracted_doc);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
EXPECT_NE(nullptr, el->findChild({}, "View3"));
|
||||
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">
|
||||
@@ -100,41 +101,41 @@ 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());
|
||||
ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
|
||||
ASSERT_EQ(2u, parser.GetExtractedInlineXmlDocuments().size());
|
||||
|
||||
xml::Element* el = xml::findRootElement(doc.get());
|
||||
xml::Element* el = xml::FindRootElement(doc.get());
|
||||
ASSERT_NE(nullptr, el);
|
||||
|
||||
EXPECT_EQ("View1", el->name);
|
||||
|
||||
xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attrText);
|
||||
xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text");
|
||||
ASSERT_NE(nullptr, attr_text);
|
||||
|
||||
xml::Attribute* attrDrawable =
|
||||
el->findAttribute(xml::kSchemaAndroid, "drawable");
|
||||
ASSERT_NE(nullptr, attrDrawable);
|
||||
xml::Attribute* attr_drawable =
|
||||
el->FindAttribute(xml::kSchemaAndroid, "drawable");
|
||||
ASSERT_NE(nullptr, attr_drawable);
|
||||
|
||||
// The two extracted resources should have different names.
|
||||
EXPECT_NE(attrText->value, attrDrawable->value);
|
||||
EXPECT_NE(attr_text->value, attr_drawable->value);
|
||||
|
||||
// The child <aapt:attr> elements should be gone.
|
||||
EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
|
||||
EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr"));
|
||||
|
||||
xml::XmlResource* extractedDocText =
|
||||
parser.getExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extractedDocText);
|
||||
el = xml::findRootElement(extractedDocText);
|
||||
xml::XmlResource* extracted_doc_text =
|
||||
parser.GetExtractedInlineXmlDocuments()[0].get();
|
||||
ASSERT_NE(nullptr, extracted_doc_text);
|
||||
el = xml::FindRootElement(extracted_doc_text);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("View2", el->name);
|
||||
|
||||
xml::XmlResource* extractedDocDrawable =
|
||||
parser.getExtractedInlineXmlDocuments()[1].get();
|
||||
ASSERT_NE(nullptr, extractedDocDrawable);
|
||||
el = xml::findRootElement(extractedDocDrawable);
|
||||
xml::XmlResource* extracted_doc_drawable =
|
||||
parser.GetExtractedInlineXmlDocuments()[1].get();
|
||||
ASSERT_NE(nullptr, extracted_doc_drawable);
|
||||
el = xml::FindRootElement(extracted_doc_drawable);
|
||||
ASSERT_NE(nullptr, el);
|
||||
EXPECT_EQ("vector", el->name);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,16 @@
|
||||
*/
|
||||
|
||||
#include "compile/Image.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
|
||||
@@ -36,7 +38,7 @@ constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
|
||||
/**
|
||||
* Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
|
||||
*/
|
||||
static uint32_t getAlpha(uint32_t color);
|
||||
static uint32_t get_alpha(uint32_t color);
|
||||
|
||||
/**
|
||||
* Determines whether a color on an ImageLine is valid.
|
||||
@@ -53,19 +55,19 @@ class ColorValidator {
|
||||
* Returns true if the color specified is a neutral color
|
||||
* (no padding, stretching, or optical bounds).
|
||||
*/
|
||||
virtual bool isNeutralColor(uint32_t color) const = 0;
|
||||
virtual bool IsNeutralColor(uint32_t color) const = 0;
|
||||
|
||||
/**
|
||||
* Returns true if the color is either a neutral color
|
||||
* or one denoting padding, stretching, or optical bounds.
|
||||
*/
|
||||
bool isValidColor(uint32_t color) const {
|
||||
bool IsValidColor(uint32_t color) const {
|
||||
switch (color) {
|
||||
case kPrimaryColor:
|
||||
case kSecondaryColor:
|
||||
return true;
|
||||
}
|
||||
return isNeutralColor(color);
|
||||
return IsNeutralColor(color);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,42 +83,43 @@ class ColorValidator {
|
||||
//
|
||||
// class ImageLine {
|
||||
// public:
|
||||
// virtual int32_t getLength() const = 0;
|
||||
// virtual uint32_t getColor(int32_t idx) const = 0;
|
||||
// virtual int32_t GetLength() const = 0;
|
||||
// virtual uint32_t GetColor(int32_t idx) const = 0;
|
||||
// };
|
||||
//
|
||||
template <typename ImageLine>
|
||||
static bool fillRanges(const ImageLine* imageLine,
|
||||
const ColorValidator* colorValidator,
|
||||
std::vector<Range>* primaryRanges,
|
||||
std::vector<Range>* secondaryRanges, std::string* err) {
|
||||
const int32_t length = imageLine->getLength();
|
||||
static bool FillRanges(const ImageLine* image_line,
|
||||
const ColorValidator* color_validator,
|
||||
std::vector<Range>* primary_ranges,
|
||||
std::vector<Range>* secondary_ranges,
|
||||
std::string* out_err) {
|
||||
const int32_t length = image_line->GetLength();
|
||||
|
||||
uint32_t lastColor = 0xffffffffu;
|
||||
uint32_t last_color = 0xffffffffu;
|
||||
for (int32_t idx = 1; idx < length - 1; idx++) {
|
||||
const uint32_t color = imageLine->getColor(idx);
|
||||
if (!colorValidator->isValidColor(color)) {
|
||||
*err = "found an invalid color";
|
||||
const uint32_t color = image_line->GetColor(idx);
|
||||
if (!color_validator->IsValidColor(color)) {
|
||||
*out_err = "found an invalid color";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (color != lastColor) {
|
||||
if (color != last_color) {
|
||||
// We are ending a range. Which range?
|
||||
// note: encode the x offset without the final 1 pixel border.
|
||||
if (lastColor == kPrimaryColor) {
|
||||
primaryRanges->back().end = idx - 1;
|
||||
} else if (lastColor == kSecondaryColor) {
|
||||
secondaryRanges->back().end = idx - 1;
|
||||
if (last_color == kPrimaryColor) {
|
||||
primary_ranges->back().end = idx - 1;
|
||||
} else if (last_color == kSecondaryColor) {
|
||||
secondary_ranges->back().end = idx - 1;
|
||||
}
|
||||
|
||||
// We are starting a range. Which range?
|
||||
// note: encode the x offset without the final 1 pixel border.
|
||||
if (color == kPrimaryColor) {
|
||||
primaryRanges->push_back(Range(idx - 1, length - 2));
|
||||
primary_ranges->push_back(Range(idx - 1, length - 2));
|
||||
} else if (color == kSecondaryColor) {
|
||||
secondaryRanges->push_back(Range(idx - 1, length - 2));
|
||||
secondary_ranges->push_back(Range(idx - 1, length - 2));
|
||||
}
|
||||
lastColor = color;
|
||||
last_color = color;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -128,19 +131,19 @@ static bool fillRanges(const ImageLine* imageLine,
|
||||
*/
|
||||
class HorizontalImageLine {
|
||||
public:
|
||||
explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
|
||||
explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
||||
int32_t length)
|
||||
: mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
|
||||
: rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
|
||||
|
||||
inline int32_t getLength() const { return mLength; }
|
||||
inline int32_t GetLength() const { return length_; }
|
||||
|
||||
inline uint32_t getColor(int32_t idx) const {
|
||||
return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
|
||||
inline uint32_t GetColor(int32_t idx) const {
|
||||
return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t** mRows;
|
||||
int32_t mXOffset, mYOffset, mLength;
|
||||
uint8_t** rows_;
|
||||
int32_t xoffset_, yoffset_, length_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
|
||||
};
|
||||
@@ -151,179 +154,180 @@ class HorizontalImageLine {
|
||||
*/
|
||||
class VerticalImageLine {
|
||||
public:
|
||||
explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
|
||||
explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
||||
int32_t length)
|
||||
: mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
|
||||
: rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
|
||||
|
||||
inline int32_t getLength() const { return mLength; }
|
||||
inline int32_t GetLength() const { return length_; }
|
||||
|
||||
inline uint32_t getColor(int32_t idx) const {
|
||||
return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
|
||||
inline uint32_t GetColor(int32_t idx) const {
|
||||
return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t** mRows;
|
||||
int32_t mXOffset, mYOffset, mLength;
|
||||
uint8_t** rows_;
|
||||
int32_t xoffset_, yoffset_, length_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
|
||||
};
|
||||
|
||||
class DiagonalImageLine {
|
||||
public:
|
||||
explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
|
||||
int32_t xStep, int32_t yStep, int32_t length)
|
||||
: mRows(rows),
|
||||
mXOffset(xOffset),
|
||||
mYOffset(yOffset),
|
||||
mXStep(xStep),
|
||||
mYStep(yStep),
|
||||
mLength(length) {}
|
||||
explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
|
||||
int32_t xstep, int32_t ystep, int32_t length)
|
||||
: rows_(rows),
|
||||
xoffset_(xoffset),
|
||||
yoffset_(yoffset),
|
||||
xstep_(xstep),
|
||||
ystep_(ystep),
|
||||
length_(length) {}
|
||||
|
||||
inline int32_t getLength() const { return mLength; }
|
||||
inline int32_t GetLength() const { return length_; }
|
||||
|
||||
inline uint32_t getColor(int32_t idx) const {
|
||||
return NinePatch::packRGBA(mRows[mYOffset + (idx * mYStep)] +
|
||||
((idx + mXOffset) * mXStep) * 4);
|
||||
inline uint32_t GetColor(int32_t idx) const {
|
||||
return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
|
||||
((idx + xoffset_) * xstep_) * 4);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t** mRows;
|
||||
int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
|
||||
uint8_t** rows_;
|
||||
int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
|
||||
};
|
||||
|
||||
class TransparentNeutralColorValidator : public ColorValidator {
|
||||
public:
|
||||
bool isNeutralColor(uint32_t color) const override {
|
||||
return getAlpha(color) == 0;
|
||||
bool IsNeutralColor(uint32_t color) const override {
|
||||
return get_alpha(color) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
class WhiteNeutralColorValidator : public ColorValidator {
|
||||
public:
|
||||
bool isNeutralColor(uint32_t color) const override {
|
||||
bool IsNeutralColor(uint32_t color) const override {
|
||||
return color == kColorOpaqueWhite;
|
||||
}
|
||||
};
|
||||
|
||||
inline static uint32_t getAlpha(uint32_t color) {
|
||||
inline static uint32_t get_alpha(uint32_t color) {
|
||||
return (color & 0xff000000u) >> 24;
|
||||
}
|
||||
|
||||
static bool populateBounds(const std::vector<Range>& padding,
|
||||
const std::vector<Range>& layoutBounds,
|
||||
const std::vector<Range>& stretchRegions,
|
||||
const int32_t length, int32_t* paddingStart,
|
||||
int32_t* paddingEnd, int32_t* layoutStart,
|
||||
int32_t* layoutEnd, const StringPiece& edgeName,
|
||||
std::string* err) {
|
||||
static bool PopulateBounds(const std::vector<Range>& padding,
|
||||
const std::vector<Range>& layout_bounds,
|
||||
const std::vector<Range>& stretch_regions,
|
||||
const int32_t length, int32_t* padding_start,
|
||||
int32_t* padding_end, int32_t* layout_start,
|
||||
int32_t* layout_end, const StringPiece& edge_name,
|
||||
std::string* out_err) {
|
||||
if (padding.size() > 1) {
|
||||
std::stringstream errStream;
|
||||
errStream << "too many padding sections on " << edgeName << " border";
|
||||
*err = errStream.str();
|
||||
std::stringstream err_stream;
|
||||
err_stream << "too many padding sections on " << edge_name << " border";
|
||||
*out_err = err_stream.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
*paddingStart = 0;
|
||||
*paddingEnd = 0;
|
||||
*padding_start = 0;
|
||||
*padding_end = 0;
|
||||
if (!padding.empty()) {
|
||||
const Range& range = padding.front();
|
||||
*paddingStart = range.start;
|
||||
*paddingEnd = length - range.end;
|
||||
} else if (!stretchRegions.empty()) {
|
||||
*padding_start = range.start;
|
||||
*padding_end = length - range.end;
|
||||
} else if (!stretch_regions.empty()) {
|
||||
// No padding was defined. Compute the padding from the first and last
|
||||
// stretch regions.
|
||||
*paddingStart = stretchRegions.front().start;
|
||||
*paddingEnd = length - stretchRegions.back().end;
|
||||
*padding_start = stretch_regions.front().start;
|
||||
*padding_end = length - stretch_regions.back().end;
|
||||
}
|
||||
|
||||
if (layoutBounds.size() > 2) {
|
||||
std::stringstream errStream;
|
||||
errStream << "too many layout bounds sections on " << edgeName << " border";
|
||||
*err = errStream.str();
|
||||
if (layout_bounds.size() > 2) {
|
||||
std::stringstream err_stream;
|
||||
err_stream << "too many layout bounds sections on " << edge_name
|
||||
<< " border";
|
||||
*out_err = err_stream.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
*layoutStart = 0;
|
||||
*layoutEnd = 0;
|
||||
if (layoutBounds.size() >= 1) {
|
||||
const Range& range = layoutBounds.front();
|
||||
*layout_start = 0;
|
||||
*layout_end = 0;
|
||||
if (layout_bounds.size() >= 1) {
|
||||
const Range& range = layout_bounds.front();
|
||||
// If there is only one layout bound segment, it might not start at 0, but
|
||||
// then it should
|
||||
// end at length.
|
||||
if (range.start != 0 && range.end != length) {
|
||||
std::stringstream errStream;
|
||||
errStream << "layout bounds on " << edgeName
|
||||
<< " border must start at edge";
|
||||
*err = errStream.str();
|
||||
std::stringstream err_stream;
|
||||
err_stream << "layout bounds on " << edge_name
|
||||
<< " border must start at edge";
|
||||
*out_err = err_stream.str();
|
||||
return false;
|
||||
}
|
||||
*layoutStart = range.end;
|
||||
*layout_start = range.end;
|
||||
|
||||
if (layoutBounds.size() >= 2) {
|
||||
const Range& range = layoutBounds.back();
|
||||
if (layout_bounds.size() >= 2) {
|
||||
const Range& range = layout_bounds.back();
|
||||
if (range.end != length) {
|
||||
std::stringstream errStream;
|
||||
errStream << "layout bounds on " << edgeName
|
||||
<< " border must start at edge";
|
||||
*err = errStream.str();
|
||||
std::stringstream err_stream;
|
||||
err_stream << "layout bounds on " << edge_name
|
||||
<< " border must start at edge";
|
||||
*out_err = err_stream.str();
|
||||
return false;
|
||||
}
|
||||
*layoutEnd = length - range.start;
|
||||
*layout_end = length - range.start;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions,
|
||||
static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
|
||||
int32_t length) {
|
||||
if (stretchRegions.size() == 0) {
|
||||
if (stretch_regions.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool startIsFixed = stretchRegions.front().start != 0;
|
||||
const bool endIsFixed = stretchRegions.back().end != length;
|
||||
const bool start_is_fixed = stretch_regions.front().start != 0;
|
||||
const bool end_is_fixed = stretch_regions.back().end != length;
|
||||
int32_t modifier = 0;
|
||||
if (startIsFixed && endIsFixed) {
|
||||
if (start_is_fixed && end_is_fixed) {
|
||||
modifier = 1;
|
||||
} else if (!startIsFixed && !endIsFixed) {
|
||||
} else if (!start_is_fixed && !end_is_fixed) {
|
||||
modifier = -1;
|
||||
}
|
||||
return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
|
||||
return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
|
||||
}
|
||||
|
||||
static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
|
||||
static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
|
||||
// Sample the first pixel to compare against.
|
||||
const uint32_t expectedColor =
|
||||
NinePatch::packRGBA(rows[region.top] + region.left * 4);
|
||||
const uint32_t expected_color =
|
||||
NinePatch::PackRGBA(rows[region.top] + region.left * 4);
|
||||
for (int32_t y = region.top; y < region.bottom; y++) {
|
||||
const uint8_t* row = rows[y];
|
||||
for (int32_t x = region.left; x < region.right; x++) {
|
||||
const uint32_t color = NinePatch::packRGBA(row + x * 4);
|
||||
if (getAlpha(color) == 0) {
|
||||
const uint32_t color = NinePatch::PackRGBA(row + x * 4);
|
||||
if (get_alpha(color) == 0) {
|
||||
// The color is transparent.
|
||||
// If the expectedColor is not transparent, NO_COLOR.
|
||||
if (getAlpha(expectedColor) != 0) {
|
||||
if (get_alpha(expected_color) != 0) {
|
||||
return android::Res_png_9patch::NO_COLOR;
|
||||
}
|
||||
} else if (color != expectedColor) {
|
||||
} else if (color != expected_color) {
|
||||
return android::Res_png_9patch::NO_COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getAlpha(expectedColor) == 0) {
|
||||
if (get_alpha(expected_color) == 0) {
|
||||
return android::Res_png_9patch::TRANSPARENT_COLOR;
|
||||
}
|
||||
return expectedColor;
|
||||
return expected_color;
|
||||
}
|
||||
|
||||
// Fills outColors with each 9-patch section's colour. If the whole section is
|
||||
// Fills out_colors with each 9-patch section's color. If the whole section is
|
||||
// transparent,
|
||||
// it gets the special TRANSPARENT colour. If the whole section is the same
|
||||
// colour, it is assigned
|
||||
// that colour. Otherwise it gets the special NO_COLOR colour.
|
||||
// it gets the special TRANSPARENT color. If the whole section is the same
|
||||
// color, it is assigned
|
||||
// that color. Otherwise it gets the special NO_COLOR color.
|
||||
//
|
||||
// Note that the rows contain the 9-patch 1px border, and the indices in the
|
||||
// stretch regions are
|
||||
@@ -332,63 +336,63 @@ static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
|
||||
// the indices must be offset by 1.
|
||||
//
|
||||
// width and height also include the 9-patch 1px border.
|
||||
static void calculateRegionColors(
|
||||
uint8_t** rows, const std::vector<Range>& horizontalStretchRegions,
|
||||
const std::vector<Range>& verticalStretchRegions, const int32_t width,
|
||||
const int32_t height, std::vector<uint32_t>* outColors) {
|
||||
int32_t nextTop = 0;
|
||||
static void CalculateRegionColors(
|
||||
uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
|
||||
const std::vector<Range>& vertical_stretch_regions, const int32_t width,
|
||||
const int32_t height, std::vector<uint32_t>* out_colors) {
|
||||
int32_t next_top = 0;
|
||||
Bounds bounds;
|
||||
auto rowIter = verticalStretchRegions.begin();
|
||||
while (nextTop != height) {
|
||||
if (rowIter != verticalStretchRegions.end()) {
|
||||
if (nextTop != rowIter->start) {
|
||||
auto row_iter = vertical_stretch_regions.begin();
|
||||
while (next_top != height) {
|
||||
if (row_iter != vertical_stretch_regions.end()) {
|
||||
if (next_top != row_iter->start) {
|
||||
// This is a fixed segment.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.top = nextTop + 1;
|
||||
bounds.bottom = rowIter->start + 1;
|
||||
nextTop = rowIter->start;
|
||||
bounds.top = next_top + 1;
|
||||
bounds.bottom = row_iter->start + 1;
|
||||
next_top = row_iter->start;
|
||||
} else {
|
||||
// This is a stretchy segment.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.top = rowIter->start + 1;
|
||||
bounds.bottom = rowIter->end + 1;
|
||||
nextTop = rowIter->end;
|
||||
++rowIter;
|
||||
bounds.top = row_iter->start + 1;
|
||||
bounds.bottom = row_iter->end + 1;
|
||||
next_top = row_iter->end;
|
||||
++row_iter;
|
||||
}
|
||||
} else {
|
||||
// This is the end, fixed section.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.top = nextTop + 1;
|
||||
bounds.top = next_top + 1;
|
||||
bounds.bottom = height + 1;
|
||||
nextTop = height;
|
||||
next_top = height;
|
||||
}
|
||||
|
||||
int32_t nextLeft = 0;
|
||||
auto colIter = horizontalStretchRegions.begin();
|
||||
while (nextLeft != width) {
|
||||
if (colIter != horizontalStretchRegions.end()) {
|
||||
if (nextLeft != colIter->start) {
|
||||
int32_t next_left = 0;
|
||||
auto col_iter = horizontal_stretch_regions.begin();
|
||||
while (next_left != width) {
|
||||
if (col_iter != horizontal_stretch_regions.end()) {
|
||||
if (next_left != col_iter->start) {
|
||||
// This is a fixed segment.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.left = nextLeft + 1;
|
||||
bounds.right = colIter->start + 1;
|
||||
nextLeft = colIter->start;
|
||||
bounds.left = next_left + 1;
|
||||
bounds.right = col_iter->start + 1;
|
||||
next_left = col_iter->start;
|
||||
} else {
|
||||
// This is a stretchy segment.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.left = colIter->start + 1;
|
||||
bounds.right = colIter->end + 1;
|
||||
nextLeft = colIter->end;
|
||||
++colIter;
|
||||
bounds.left = col_iter->start + 1;
|
||||
bounds.right = col_iter->end + 1;
|
||||
next_left = col_iter->end;
|
||||
++col_iter;
|
||||
}
|
||||
} else {
|
||||
// This is the end, fixed section.
|
||||
// Offset the bounds by 1 to accommodate the border.
|
||||
bounds.left = nextLeft + 1;
|
||||
bounds.left = next_left + 1;
|
||||
bounds.right = width + 1;
|
||||
nextLeft = width;
|
||||
next_left = width;
|
||||
}
|
||||
outColors->push_back(getRegionColor(rows, bounds));
|
||||
out_colors->push_back(GetRegionColor(rows, bounds));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,12 +401,12 @@ static void calculateRegionColors(
|
||||
// alpha value begins
|
||||
// (on both sides).
|
||||
template <typename ImageLine>
|
||||
static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart,
|
||||
int32_t* outEnd) {
|
||||
*outStart = 0;
|
||||
*outEnd = 0;
|
||||
static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
|
||||
int32_t* out_end) {
|
||||
*out_start = 0;
|
||||
*out_end = 0;
|
||||
|
||||
const int32_t length = imageLine->getLength();
|
||||
const int32_t length = image_line->GetLength();
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
@@ -413,179 +417,181 @@ static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart,
|
||||
const int32_t mid2 = length / 2;
|
||||
const int32_t mid1 = mid2 + (length % 2);
|
||||
|
||||
uint32_t maxAlpha = 0;
|
||||
for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
|
||||
uint32_t alpha = getAlpha(imageLine->getColor(i));
|
||||
if (alpha > maxAlpha) {
|
||||
maxAlpha = alpha;
|
||||
*outStart = i;
|
||||
uint32_t max_alpha = 0;
|
||||
for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
|
||||
uint32_t alpha = get_alpha(image_line->GetColor(i));
|
||||
if (alpha > max_alpha) {
|
||||
max_alpha = alpha;
|
||||
*out_start = i;
|
||||
}
|
||||
}
|
||||
|
||||
maxAlpha = 0;
|
||||
for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
|
||||
uint32_t alpha = getAlpha(imageLine->getColor(i));
|
||||
if (alpha > maxAlpha) {
|
||||
maxAlpha = alpha;
|
||||
*outEnd = length - (i + 1);
|
||||
max_alpha = 0;
|
||||
for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
|
||||
uint32_t alpha = get_alpha(image_line->GetColor(i));
|
||||
if (alpha > max_alpha) {
|
||||
max_alpha = alpha;
|
||||
*out_end = length - (i + 1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename ImageLine>
|
||||
static uint32_t findMaxAlpha(const ImageLine* imageLine) {
|
||||
const int32_t length = imageLine->getLength();
|
||||
uint32_t maxAlpha = 0;
|
||||
for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
|
||||
uint32_t alpha = getAlpha(imageLine->getColor(idx));
|
||||
if (alpha > maxAlpha) {
|
||||
maxAlpha = alpha;
|
||||
static uint32_t FindMaxAlpha(const ImageLine* image_line) {
|
||||
const int32_t length = image_line->GetLength();
|
||||
uint32_t max_alpha = 0;
|
||||
for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
|
||||
uint32_t alpha = get_alpha(image_line->GetColor(idx));
|
||||
if (alpha > max_alpha) {
|
||||
max_alpha = alpha;
|
||||
}
|
||||
}
|
||||
return maxAlpha;
|
||||
return max_alpha;
|
||||
}
|
||||
|
||||
// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
|
||||
uint32_t NinePatch::packRGBA(const uint8_t* pixel) {
|
||||
uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
|
||||
return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
|
||||
}
|
||||
|
||||
std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
|
||||
std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
|
||||
const int32_t width,
|
||||
const int32_t height,
|
||||
std::string* err) {
|
||||
std::string* out_err) {
|
||||
if (width < 3 || height < 3) {
|
||||
*err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
|
||||
*out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Range> horizontalPadding;
|
||||
std::vector<Range> horizontalOpticalBounds;
|
||||
std::vector<Range> verticalPadding;
|
||||
std::vector<Range> verticalOpticalBounds;
|
||||
std::vector<Range> unexpectedRanges;
|
||||
std::unique_ptr<ColorValidator> colorValidator;
|
||||
std::vector<Range> horizontal_padding;
|
||||
std::vector<Range> horizontal_layout_bounds;
|
||||
std::vector<Range> vertical_padding;
|
||||
std::vector<Range> vertical_layout_bounds;
|
||||
std::vector<Range> unexpected_ranges;
|
||||
std::unique_ptr<ColorValidator> color_validator;
|
||||
|
||||
if (rows[0][3] == 0) {
|
||||
colorValidator = util::make_unique<TransparentNeutralColorValidator>();
|
||||
} else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
|
||||
colorValidator = util::make_unique<WhiteNeutralColorValidator>();
|
||||
color_validator = util::make_unique<TransparentNeutralColorValidator>();
|
||||
} else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
|
||||
color_validator = util::make_unique<WhiteNeutralColorValidator>();
|
||||
} else {
|
||||
*err = "top-left corner pixel must be either opaque white or transparent";
|
||||
*out_err =
|
||||
"top-left corner pixel must be either opaque white or transparent";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Private constructor, can't use make_unique.
|
||||
auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
|
||||
auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
|
||||
|
||||
HorizontalImageLine topRow(rows, 0, 0, width);
|
||||
if (!fillRanges(&topRow, colorValidator.get(),
|
||||
&ninePatch->horizontalStretchRegions, &unexpectedRanges,
|
||||
err)) {
|
||||
HorizontalImageLine top_row(rows, 0, 0, width);
|
||||
if (!FillRanges(&top_row, color_validator.get(),
|
||||
&nine_patch->horizontal_stretch_regions, &unexpected_ranges,
|
||||
out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!unexpectedRanges.empty()) {
|
||||
const Range& range = unexpectedRanges[0];
|
||||
std::stringstream errStream;
|
||||
errStream << "found unexpected optical bounds (red pixel) on top border "
|
||||
<< "at x=" << range.start + 1;
|
||||
*err = errStream.str();
|
||||
if (!unexpected_ranges.empty()) {
|
||||
const Range& range = unexpected_ranges[0];
|
||||
std::stringstream err_stream;
|
||||
err_stream << "found unexpected optical bounds (red pixel) on top border "
|
||||
<< "at x=" << range.start + 1;
|
||||
*out_err = err_stream.str();
|
||||
return {};
|
||||
}
|
||||
|
||||
VerticalImageLine leftCol(rows, 0, 0, height);
|
||||
if (!fillRanges(&leftCol, colorValidator.get(),
|
||||
&ninePatch->verticalStretchRegions, &unexpectedRanges, err)) {
|
||||
VerticalImageLine left_col(rows, 0, 0, height);
|
||||
if (!FillRanges(&left_col, color_validator.get(),
|
||||
&nine_patch->vertical_stretch_regions, &unexpected_ranges,
|
||||
out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!unexpectedRanges.empty()) {
|
||||
const Range& range = unexpectedRanges[0];
|
||||
std::stringstream errStream;
|
||||
errStream << "found unexpected optical bounds (red pixel) on left border "
|
||||
<< "at y=" << range.start + 1;
|
||||
if (!unexpected_ranges.empty()) {
|
||||
const Range& range = unexpected_ranges[0];
|
||||
std::stringstream err_stream;
|
||||
err_stream << "found unexpected optical bounds (red pixel) on left border "
|
||||
<< "at y=" << range.start + 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
HorizontalImageLine bottomRow(rows, 0, height - 1, width);
|
||||
if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
|
||||
&horizontalOpticalBounds, err)) {
|
||||
HorizontalImageLine bottom_row(rows, 0, height - 1, width);
|
||||
if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
|
||||
&horizontal_layout_bounds, out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
|
||||
ninePatch->horizontalStretchRegions, width - 2,
|
||||
&ninePatch->padding.left, &ninePatch->padding.right,
|
||||
&ninePatch->layoutBounds.left,
|
||||
&ninePatch->layoutBounds.right, "bottom", err)) {
|
||||
if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
|
||||
nine_patch->horizontal_stretch_regions, width - 2,
|
||||
&nine_patch->padding.left, &nine_patch->padding.right,
|
||||
&nine_patch->layout_bounds.left,
|
||||
&nine_patch->layout_bounds.right, "bottom", out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VerticalImageLine rightCol(rows, width - 1, 0, height);
|
||||
if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
|
||||
&verticalOpticalBounds, err)) {
|
||||
VerticalImageLine right_col(rows, width - 1, 0, height);
|
||||
if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
|
||||
&vertical_layout_bounds, out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!populateBounds(verticalPadding, verticalOpticalBounds,
|
||||
ninePatch->verticalStretchRegions, height - 2,
|
||||
&ninePatch->padding.top, &ninePatch->padding.bottom,
|
||||
&ninePatch->layoutBounds.top,
|
||||
&ninePatch->layoutBounds.bottom, "right", err)) {
|
||||
if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
|
||||
nine_patch->vertical_stretch_regions, height - 2,
|
||||
&nine_patch->padding.top, &nine_patch->padding.bottom,
|
||||
&nine_patch->layout_bounds.top,
|
||||
&nine_patch->layout_bounds.bottom, "right", out_err)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Fill the region colors of the 9-patch.
|
||||
const int32_t numRows =
|
||||
calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
|
||||
const int32_t numCols =
|
||||
calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
|
||||
if ((int64_t)numRows * (int64_t)numCols > 0x7f) {
|
||||
*err = "too many regions in 9-patch";
|
||||
const int32_t num_rows =
|
||||
CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
|
||||
const int32_t num_cols =
|
||||
CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
|
||||
if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
|
||||
*out_err = "too many regions in 9-patch";
|
||||
return {};
|
||||
}
|
||||
|
||||
ninePatch->regionColors.reserve(numRows * numCols);
|
||||
calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
|
||||
ninePatch->verticalStretchRegions, width - 2,
|
||||
height - 2, &ninePatch->regionColors);
|
||||
nine_patch->region_colors.reserve(num_rows * num_cols);
|
||||
CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
|
||||
nine_patch->vertical_stretch_regions, width - 2,
|
||||
height - 2, &nine_patch->region_colors);
|
||||
|
||||
// Compute the outline based on opacity.
|
||||
|
||||
// Find left and right extent of 9-patch content on center row.
|
||||
HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
|
||||
findOutlineInsets(&midRow, &ninePatch->outline.left,
|
||||
&ninePatch->outline.right);
|
||||
HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
|
||||
FindOutlineInsets(&mid_row, &nine_patch->outline.left,
|
||||
&nine_patch->outline.right);
|
||||
|
||||
// Find top and bottom extent of 9-patch content on center column.
|
||||
VerticalImageLine midCol(rows, width / 2, 1, height - 2);
|
||||
findOutlineInsets(&midCol, &ninePatch->outline.top,
|
||||
&ninePatch->outline.bottom);
|
||||
VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
|
||||
FindOutlineInsets(&mid_col, &nine_patch->outline.top,
|
||||
&nine_patch->outline.bottom);
|
||||
|
||||
const int32_t outlineWidth =
|
||||
(width - 2) - ninePatch->outline.left - ninePatch->outline.right;
|
||||
const int32_t outlineHeight =
|
||||
(height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
|
||||
const int32_t outline_width =
|
||||
(width - 2) - nine_patch->outline.left - nine_patch->outline.right;
|
||||
const int32_t outline_height =
|
||||
(height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
|
||||
|
||||
// Find the largest alpha value within the outline area.
|
||||
HorizontalImageLine outlineMidRow(
|
||||
rows, 1 + ninePatch->outline.left,
|
||||
1 + ninePatch->outline.top + (outlineHeight / 2), outlineWidth);
|
||||
VerticalImageLine outlineMidCol(
|
||||
rows, 1 + ninePatch->outline.left + (outlineWidth / 2),
|
||||
1 + ninePatch->outline.top, outlineHeight);
|
||||
ninePatch->outlineAlpha =
|
||||
std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
|
||||
HorizontalImageLine outline_mid_row(
|
||||
rows, 1 + nine_patch->outline.left,
|
||||
1 + nine_patch->outline.top + (outline_height / 2), outline_width);
|
||||
VerticalImageLine outline_mid_col(
|
||||
rows, 1 + nine_patch->outline.left + (outline_width / 2),
|
||||
1 + nine_patch->outline.top, outline_height);
|
||||
nine_patch->outline_alpha =
|
||||
std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
|
||||
|
||||
// Assuming the image is a round rect, compute the radius by marching
|
||||
// diagonally from the top left corner towards the center.
|
||||
DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left,
|
||||
1 + ninePatch->outline.top, 1, 1,
|
||||
std::min(outlineWidth, outlineHeight));
|
||||
int32_t topLeft, bottomRight;
|
||||
findOutlineInsets(&diagonal, &topLeft, &bottomRight);
|
||||
DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
|
||||
1 + nine_patch->outline.top, 1, 1,
|
||||
std::min(outline_width, outline_height));
|
||||
int32_t top_left, bottom_right;
|
||||
FindOutlineInsets(&diagonal, &top_left, &bottom_right);
|
||||
|
||||
/* Determine source radius based upon inset:
|
||||
* sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
|
||||
@@ -593,15 +599,15 @@ std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
|
||||
* (sqrt(2) - 1) * r = sqrt(2) * i
|
||||
* r = sqrt(2) / (sqrt(2) - 1) * i
|
||||
*/
|
||||
ninePatch->outlineRadius = 3.4142f * topLeft;
|
||||
return ninePatch;
|
||||
nine_patch->outline_radius = 3.4142f * top_left;
|
||||
return nine_patch;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
|
||||
std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
|
||||
android::Res_png_9patch data;
|
||||
data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
|
||||
data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
|
||||
data.numColors = static_cast<uint8_t>(regionColors.size());
|
||||
data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
|
||||
data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
|
||||
data.numColors = static_cast<uint8_t>(region_colors.size());
|
||||
data.paddingLeft = padding.left;
|
||||
data.paddingRight = padding.right;
|
||||
data.paddingTop = padding.top;
|
||||
@@ -609,8 +615,8 @@ std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
|
||||
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
|
||||
android::Res_png_9patch::serialize(
|
||||
data, (const int32_t*)horizontalStretchRegions.data(),
|
||||
(const int32_t*)verticalStretchRegions.data(), regionColors.data(),
|
||||
data, (const int32_t*)horizontal_stretch_regions.data(),
|
||||
(const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
|
||||
buffer.get());
|
||||
// Convert to file endianness.
|
||||
reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
|
||||
@@ -619,32 +625,32 @@ std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(
|
||||
size_t* outLen) const {
|
||||
size_t chunkLen = sizeof(uint32_t) * 4;
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
|
||||
std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
|
||||
size_t* out_len) const {
|
||||
size_t chunk_len = sizeof(uint32_t) * 4;
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
|
||||
uint8_t* cursor = buffer.get();
|
||||
|
||||
memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
|
||||
cursor += sizeof(layoutBounds.left);
|
||||
memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
|
||||
cursor += sizeof(layout_bounds.left);
|
||||
|
||||
memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
|
||||
cursor += sizeof(layoutBounds.top);
|
||||
memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
|
||||
cursor += sizeof(layout_bounds.top);
|
||||
|
||||
memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
|
||||
cursor += sizeof(layoutBounds.right);
|
||||
memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
|
||||
cursor += sizeof(layout_bounds.right);
|
||||
|
||||
memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
|
||||
cursor += sizeof(layoutBounds.bottom);
|
||||
memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
|
||||
cursor += sizeof(layout_bounds.bottom);
|
||||
|
||||
*outLen = chunkLen;
|
||||
*out_len = chunk_len;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
|
||||
size_t* outLen) const {
|
||||
size_t chunkLen = sizeof(uint32_t) * 6;
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
|
||||
std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
|
||||
size_t* out_len) const {
|
||||
size_t chunk_len = sizeof(uint32_t) * 6;
|
||||
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
|
||||
uint8_t* cursor = buffer.get();
|
||||
|
||||
memcpy(cursor, &outline.left, sizeof(outline.left));
|
||||
@@ -659,12 +665,12 @@ std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
|
||||
memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
|
||||
cursor += sizeof(outline.bottom);
|
||||
|
||||
*((float*)cursor) = outlineRadius;
|
||||
cursor += sizeof(outlineRadius);
|
||||
*((float*)cursor) = outline_radius;
|
||||
cursor += sizeof(outline_radius);
|
||||
|
||||
*((uint32_t*)cursor) = outlineAlpha;
|
||||
*((uint32_t*)cursor) = outline_alpha;
|
||||
|
||||
*outLen = chunkLen;
|
||||
*out_len = chunk_len;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -677,16 +683,16 @@ std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
|
||||
<< " r=" << bounds.right << " b=" << bounds.bottom;
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) {
|
||||
::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
|
||||
return out << "horizontalStretch:"
|
||||
<< util::joiner(ninePatch.horizontalStretchRegions, " ")
|
||||
<< util::Joiner(nine_patch.horizontal_stretch_regions, " ")
|
||||
<< " verticalStretch:"
|
||||
<< util::joiner(ninePatch.verticalStretchRegions, " ")
|
||||
<< " padding: " << ninePatch.padding
|
||||
<< ", bounds: " << ninePatch.layoutBounds
|
||||
<< ", outline: " << ninePatch.outline
|
||||
<< " rad=" << ninePatch.outlineRadius
|
||||
<< " alpha=" << ninePatch.outlineAlpha;
|
||||
<< util::Joiner(nine_patch.vertical_stretch_regions, " ")
|
||||
<< " padding: " << nine_patch.padding
|
||||
<< ", bounds: " << nine_patch.layout_bounds
|
||||
<< ", outline: " << nine_patch.outline
|
||||
<< " rad=" << nine_patch.outline_radius
|
||||
<< " alpha=" << nine_patch.outline_alpha;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "compile/Image.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
@@ -182,169 +183,168 @@ static uint8_t* kStretchAndPadding5x5[] = {
|
||||
|
||||
TEST(NinePatchTest, Minimum3x3) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &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_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));
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
|
||||
ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
|
||||
ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
|
||||
ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size());
|
||||
|
||||
EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
|
||||
EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
|
||||
EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front());
|
||||
EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front());
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, MultipleStretchRegions) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
|
||||
ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
|
||||
ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
|
||||
ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
|
||||
ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.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), nine_patch->horizontal_stretch_regions[0]);
|
||||
EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]);
|
||||
EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]);
|
||||
|
||||
EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
|
||||
EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
|
||||
EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]);
|
||||
EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kPadding6x5, 6, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
|
||||
std::string err;
|
||||
EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &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));
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
|
||||
|
||||
ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
|
||||
nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds);
|
||||
}
|
||||
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
|
||||
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, RegionColorsAreCorrect) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kColorfulImage5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
|
||||
std::vector<uint32_t> expectedColors = {
|
||||
NinePatch::packRGBA((uint8_t*)RED),
|
||||
std::vector<uint32_t> expected_colors = {
|
||||
NinePatch::PackRGBA((uint8_t*)RED),
|
||||
(uint32_t)android::Res_png_9patch::NO_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*)GREEN),
|
||||
NinePatch::PackRGBA((uint8_t*)GREEN),
|
||||
(uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
|
||||
NinePatch::packRGBA((uint8_t*)BLUE),
|
||||
NinePatch::packRGBA((uint8_t*)GREEN),
|
||||
NinePatch::PackRGBA((uint8_t*)BLUE),
|
||||
NinePatch::PackRGBA((uint8_t*)GREEN),
|
||||
};
|
||||
EXPECT_EQ(expectedColors, ninePatch->regionColors);
|
||||
EXPECT_EQ(expected_colors, nine_patch->region_colors);
|
||||
}
|
||||
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
|
||||
EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
|
||||
EXPECT_EQ(0.0f, nine_patch->outline_radius);
|
||||
}
|
||||
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
|
||||
EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
|
||||
EXPECT_EQ(0.0f, nine_patch->outline_radius);
|
||||
}
|
||||
|
||||
TEST(NinePatchTest, OutlineFromOffCenterImage) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
// but until I know what behavior I'm breaking, I will leave it at the
|
||||
// incorrect:
|
||||
EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
|
||||
EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline);
|
||||
|
||||
EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
|
||||
EXPECT_EQ(0.0f, ninePatch->outlineRadius);
|
||||
EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
|
||||
EXPECT_EQ(0.0f, nine_patch->outline_radius);
|
||||
}
|
||||
|
||||
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::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
|
||||
EXPECT_EQ(3.4142f, nine_patch->outline_radius);
|
||||
}
|
||||
|
||||
::testing::AssertionResult bigEndianOne(uint8_t* cursor) {
|
||||
::testing::AssertionResult BigEndianOne(uint8_t* cursor) {
|
||||
if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
@@ -353,12 +353,12 @@ TEST(NinePatchTest, OutlineRadius) {
|
||||
|
||||
TEST(NinePatchTest, SerializePngEndianness) {
|
||||
std::string err;
|
||||
std::unique_ptr<NinePatch> ninePatch =
|
||||
NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, ninePatch);
|
||||
std::unique_ptr<NinePatch> nine_patch =
|
||||
NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
|
||||
ASSERT_NE(nullptr, nine_patch);
|
||||
|
||||
size_t len;
|
||||
std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
|
||||
std::unique_ptr<uint8_t[]> data = nine_patch->SerializeBase(&len);
|
||||
ASSERT_NE(nullptr, data);
|
||||
ASSERT_NE(0u, len);
|
||||
|
||||
@@ -368,10 +368,10 @@ TEST(NinePatchTest, SerializePngEndianness) {
|
||||
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));
|
||||
EXPECT_TRUE(BigEndianOne(cursor));
|
||||
EXPECT_TRUE(BigEndianOne(cursor + 4));
|
||||
EXPECT_TRUE(BigEndianOne(cursor + 8));
|
||||
EXPECT_TRUE(BigEndianOne(cursor + 12));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -89,7 +89,7 @@ static void readDataFromStream(png_structp readPtr, png_bytep data,
|
||||
static void writeDataToStream(png_structp writePtr, png_bytep data,
|
||||
png_size_t length) {
|
||||
BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
|
||||
png_bytep buf = outBuffer->nextBlock<png_byte>(length);
|
||||
png_bytep buf = outBuffer->NextBlock<png_byte>(length);
|
||||
memcpy(buf, data, length);
|
||||
}
|
||||
|
||||
@@ -98,13 +98,13 @@ static void flushDataToStream(png_structp /*writePtr*/) {}
|
||||
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
|
||||
IDiagnostics* diag =
|
||||
reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
|
||||
diag->warn(DiagMessage() << warningMessage);
|
||||
diag->Warn(DiagMessage() << warningMessage);
|
||||
}
|
||||
|
||||
static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
|
||||
PngInfo* outInfo) {
|
||||
if (setjmp(png_jmpbuf(readPtr))) {
|
||||
diag->error(DiagMessage() << "failed reading png");
|
||||
diag->Error(DiagMessage() << "failed reading png");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo,
|
||||
*colorType = PNG_COLOR_TYPE_PALETTE;
|
||||
} else {
|
||||
if (maxGrayDeviation <= grayscaleTolerance) {
|
||||
diag->note(DiagMessage() << "forcing image to gray (max deviation = "
|
||||
diag->Note(DiagMessage() << "forcing image to gray (max deviation = "
|
||||
<< maxGrayDeviation << ")");
|
||||
*colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
} else {
|
||||
@@ -424,7 +424,7 @@ static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo,
|
||||
static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
||||
png_infop infoPtr, PngInfo* info, int grayScaleTolerance) {
|
||||
if (setjmp(png_jmpbuf(writePtr))) {
|
||||
diag->error(DiagMessage() << "failed to write png");
|
||||
diag->Error(DiagMessage() << "failed to write png");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
||||
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
|
||||
|
||||
if (kDebug) {
|
||||
diag->note(DiagMessage() << "writing image: w = " << info->width
|
||||
diag->Note(DiagMessage() << "writing image: w = " << info->width
|
||||
<< ", h = " << info->height);
|
||||
}
|
||||
|
||||
@@ -476,23 +476,23 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
||||
if (kDebug) {
|
||||
switch (colorType) {
|
||||
case PNG_COLOR_TYPE_PALETTE:
|
||||
diag->note(DiagMessage() << "has " << paletteEntries << " colors"
|
||||
diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
|
||||
<< (hasTransparency ? " (with alpha)" : "")
|
||||
<< ", using PNG_COLOR_TYPE_PALLETTE");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
diag->note(DiagMessage()
|
||||
diag->Note(DiagMessage()
|
||||
<< "is opaque gray, using PNG_COLOR_TYPE_GRAY");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
diag->note(DiagMessage()
|
||||
diag->Note(DiagMessage()
|
||||
<< "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_RGB:
|
||||
diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
|
||||
diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
|
||||
break;
|
||||
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
diag->note(DiagMessage()
|
||||
diag->Note(DiagMessage()
|
||||
<< "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
|
||||
break;
|
||||
}
|
||||
@@ -527,7 +527,7 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
||||
|
||||
// base 9 patch data
|
||||
if (kDebug) {
|
||||
diag->note(DiagMessage() << "adding 9-patch info..");
|
||||
diag->Note(DiagMessage() << "adding 9-patch info..");
|
||||
}
|
||||
strcpy((char*)unknowns[pIndex].name, "npTc");
|
||||
unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
|
||||
@@ -604,7 +604,7 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr,
|
||||
&interlaceType, &compressionType, nullptr);
|
||||
|
||||
if (kDebug) {
|
||||
diag->note(DiagMessage() << "image written: w = " << width
|
||||
diag->Note(DiagMessage() << "image written: w = " << width
|
||||
<< ", h = " << height << ", d = " << bitDepth
|
||||
<< ", colors = " << colorType
|
||||
<< ", inter = " << interlaceType
|
||||
@@ -1228,13 +1228,13 @@ bool Png::process(const Source& source, std::istream* input,
|
||||
|
||||
// Read the PNG signature first.
|
||||
if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
|
||||
mDiag->error(DiagMessage() << strerror(errno));
|
||||
mDiag->Error(DiagMessage() << strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the PNG signature doesn't match, bail early.
|
||||
if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
|
||||
mDiag->error(DiagMessage() << "not a valid png file");
|
||||
mDiag->Error(DiagMessage() << "not a valid png file");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1247,13 +1247,13 @@ bool Png::process(const Source& source, std::istream* input,
|
||||
|
||||
readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
||||
if (!readPtr) {
|
||||
mDiag->error(DiagMessage() << "failed to allocate read ptr");
|
||||
mDiag->Error(DiagMessage() << "failed to allocate read ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
infoPtr = png_create_info_struct(readPtr);
|
||||
if (!infoPtr) {
|
||||
mDiag->error(DiagMessage() << "failed to allocate info ptr");
|
||||
mDiag->Error(DiagMessage() << "failed to allocate info ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
@@ -1267,10 +1267,10 @@ bool Png::process(const Source& source, std::istream* input,
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (util::stringEndsWith(source.path, ".9.png")) {
|
||||
if (util::EndsWith(source.path, ".9.png")) {
|
||||
std::string errorMsg;
|
||||
if (!do9Patch(&pngInfo, &errorMsg)) {
|
||||
mDiag->error(DiagMessage() << errorMsg);
|
||||
mDiag->Error(DiagMessage() << errorMsg);
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
@@ -1278,13 +1278,13 @@ bool Png::process(const Source& source, std::istream* input,
|
||||
writePtr =
|
||||
png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
|
||||
if (!writePtr) {
|
||||
mDiag->error(DiagMessage() << "failed to allocate write ptr");
|
||||
mDiag->Error(DiagMessage() << "failed to allocate write ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
writeInfoPtr = png_create_info_struct(writePtr);
|
||||
if (!writeInfoPtr) {
|
||||
mDiag->error(DiagMessage() << "failed to allocate write info ptr");
|
||||
mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
@@ -1295,7 +1295,7 @@ bool Png::process(const Source& source, std::istream* input,
|
||||
flushDataToStream);
|
||||
|
||||
if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
|
||||
options.grayScaleTolerance)) {
|
||||
options.grayscale_tolerance)) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#ifndef AAPT_PNG_H
|
||||
#define AAPT_PNG_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "Source.h"
|
||||
#include "compile/Image.h"
|
||||
@@ -24,16 +29,15 @@
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/BigBuffer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct PngOptions {
|
||||
int grayScaleTolerance = 0;
|
||||
int grayscale_tolerance = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deprecated. Removing once new PNG crunching code is proved to be correct.
|
||||
*/
|
||||
class Png {
|
||||
public:
|
||||
explicit Png(IDiagnostics* diag) : mDiag(diag) {}
|
||||
@@ -59,18 +63,18 @@ class PngChunkFilter : public io::InputStream {
|
||||
bool Skip(int count) override;
|
||||
|
||||
int64_t ByteCount() const override {
|
||||
return static_cast<int64_t>(mWindowStart);
|
||||
return static_cast<int64_t>(window_start_);
|
||||
}
|
||||
|
||||
bool HadError() const override { return mError; }
|
||||
bool HadError() const override { return error_; }
|
||||
|
||||
private:
|
||||
bool consumeWindow(const void** buffer, int* len);
|
||||
bool ConsumeWindow(const void** buffer, int* len);
|
||||
|
||||
StringPiece mData;
|
||||
size_t mWindowStart = 0;
|
||||
size_t mWindowEnd = 0;
|
||||
bool mError = false;
|
||||
StringPiece data_;
|
||||
size_t window_start_ = 0;
|
||||
size_t window_end_ = 0;
|
||||
bool error_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
|
||||
};
|
||||
@@ -78,14 +82,14 @@ class PngChunkFilter : public io::InputStream {
|
||||
/**
|
||||
* Reads a PNG from the InputStream into memory as an RGBA Image.
|
||||
*/
|
||||
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in);
|
||||
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.
|
||||
*/
|
||||
bool writePng(IAaptContext* context, const Image* image,
|
||||
const NinePatch* ninePatch, io::OutputStream* out,
|
||||
bool WritePng(IAaptContext* context, const Image* image,
|
||||
const NinePatch* nine_patch, io::OutputStream* out,
|
||||
const PngOptions& options);
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "compile/Png.h"
|
||||
|
||||
#include "io/Io.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
@@ -39,7 +40,7 @@ enum PngChunkTypes {
|
||||
kPngChunksRGB = u32(115, 82, 71, 66),
|
||||
};
|
||||
|
||||
static uint32_t peek32LE(const char* data) {
|
||||
static uint32_t Peek32LE(const char* data) {
|
||||
uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
|
||||
word <<= 8;
|
||||
word |= ((uint32_t)data[1]) & 0x000000ff;
|
||||
@@ -50,7 +51,7 @@ static uint32_t peek32LE(const char* data) {
|
||||
return word;
|
||||
}
|
||||
|
||||
static bool isPngChunkWhitelisted(uint32_t type) {
|
||||
static bool IsPngChunkWhitelisted(uint32_t type) {
|
||||
switch (type) {
|
||||
case kPngChunkIHDR:
|
||||
case kPngChunkIDAT:
|
||||
@@ -64,93 +65,93 @@ static bool isPngChunkWhitelisted(uint32_t type) {
|
||||
}
|
||||
}
|
||||
|
||||
PngChunkFilter::PngChunkFilter(const StringPiece& data) : mData(data) {
|
||||
if (util::stringStartsWith(mData, kPngSignature)) {
|
||||
mWindowStart = 0;
|
||||
mWindowEnd = strlen(kPngSignature);
|
||||
PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
|
||||
if (util::StartsWith(data_, kPngSignature)) {
|
||||
window_start_ = 0;
|
||||
window_end_ = strlen(kPngSignature);
|
||||
} else {
|
||||
mError = true;
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PngChunkFilter::consumeWindow(const void** buffer, int* len) {
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
|
||||
if (window_start_ != window_end_) {
|
||||
// We have bytes to give from our window.
|
||||
const int bytesRead = (int)(mWindowEnd - mWindowStart);
|
||||
*buffer = mData.data() + mWindowStart;
|
||||
*len = bytesRead;
|
||||
mWindowStart = mWindowEnd;
|
||||
const int bytes_read = (int)(window_end_ - window_start_);
|
||||
*buffer = data_.data() + window_start_;
|
||||
*len = bytes_read;
|
||||
window_start_ = window_end_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PngChunkFilter::Next(const void** buffer, int* len) {
|
||||
if (mError) {
|
||||
if (error_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In case BackUp was called, we must consume the window.
|
||||
if (consumeWindow(buffer, len)) {
|
||||
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()) {
|
||||
while (window_end_ < data_.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;
|
||||
if (data_.size() - window_start_ < kMinChunkHeaderSize) {
|
||||
error_ = 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()) {
|
||||
const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
|
||||
if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
|
||||
data_.size()) {
|
||||
// Overflow.
|
||||
mError = true;
|
||||
error_ = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we strip this chunk?
|
||||
const uint32_t chunkType =
|
||||
peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
|
||||
if (isPngChunkWhitelisted(chunkType)) {
|
||||
const uint32_t chunk_type =
|
||||
Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
|
||||
if (IsPngChunkWhitelisted(chunk_type)) {
|
||||
// Advance the window to include this chunk.
|
||||
mWindowEnd += kMinChunkHeaderSize + chunkLen;
|
||||
window_end_ += kMinChunkHeaderSize + chunk_len;
|
||||
} else {
|
||||
// We want to strip this chunk. If we accumulated a window,
|
||||
// we must return the window now.
|
||||
if (mWindowStart != mWindowEnd) {
|
||||
if (window_start_ != window_end_) {
|
||||
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;
|
||||
window_end_ += kMinChunkHeaderSize + chunk_len;
|
||||
window_start_ = window_end_;
|
||||
}
|
||||
}
|
||||
|
||||
if (consumeWindow(buffer, len)) {
|
||||
if (ConsumeWindow(buffer, len)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PngChunkFilter::BackUp(int count) {
|
||||
if (mError) {
|
||||
if (error_) {
|
||||
return;
|
||||
}
|
||||
mWindowStart -= count;
|
||||
window_start_ -= count;
|
||||
}
|
||||
|
||||
bool PngChunkFilter::Skip(int count) {
|
||||
if (mError) {
|
||||
if (error_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@
|
||||
|
||||
#include "compile/Png.h"
|
||||
|
||||
#include <android-base/errors.h>
|
||||
#include <android-base/macros.h>
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "android-base/errors.h"
|
||||
#include "android-base/logging.h"
|
||||
#include "android-base/macros.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// Size in bytes of the PNG signature.
|
||||
@@ -34,16 +37,16 @@ constexpr size_t kPngSignatureSize = 8u;
|
||||
*/
|
||||
class PngReadStructDeleter {
|
||||
public:
|
||||
explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr)
|
||||
: mReadPtr(readPtr), mInfoPtr(infoPtr) {}
|
||||
PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
|
||||
: read_ptr_(read_ptr), info_ptr_(info_ptr) {}
|
||||
|
||||
~PngReadStructDeleter() {
|
||||
png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
|
||||
png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
png_structp mReadPtr;
|
||||
png_infop mInfoPtr;
|
||||
png_structp read_ptr_;
|
||||
png_infop info_ptr_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
|
||||
};
|
||||
@@ -53,226 +56,229 @@ class PngReadStructDeleter {
|
||||
*/
|
||||
class PngWriteStructDeleter {
|
||||
public:
|
||||
explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr)
|
||||
: mWritePtr(writePtr), mInfoPtr(infoPtr) {}
|
||||
PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
|
||||
: write_ptr_(write_ptr), info_ptr_(info_ptr) {}
|
||||
|
||||
~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); }
|
||||
~PngWriteStructDeleter() {
|
||||
png_destroy_write_struct(&write_ptr_, &info_ptr_);
|
||||
}
|
||||
|
||||
private:
|
||||
png_structp mWritePtr;
|
||||
png_infop mInfoPtr;
|
||||
png_structp write_ptr_;
|
||||
png_infop info_ptr_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
|
||||
};
|
||||
|
||||
// Custom warning logging method that uses IDiagnostics.
|
||||
static void logWarning(png_structp pngPtr, png_const_charp warningMsg) {
|
||||
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
|
||||
diag->warn(DiagMessage() << warningMsg);
|
||||
static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
|
||||
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
|
||||
diag->Warn(DiagMessage() << warning_msg);
|
||||
}
|
||||
|
||||
// Custom error logging method that uses IDiagnostics.
|
||||
static void logError(png_structp pngPtr, png_const_charp errorMsg) {
|
||||
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
|
||||
diag->error(DiagMessage() << errorMsg);
|
||||
static void LogError(png_structp png_ptr, png_const_charp error_msg) {
|
||||
IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
|
||||
diag->Error(DiagMessage() << error_msg);
|
||||
}
|
||||
|
||||
static void readDataFromStream(png_structp pngPtr, png_bytep buffer,
|
||||
static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
|
||||
png_size_t len) {
|
||||
io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr);
|
||||
io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
|
||||
|
||||
const void* inBuffer;
|
||||
int inLen;
|
||||
if (!in->Next(&inBuffer, &inLen)) {
|
||||
const void* in_buffer;
|
||||
int in_len;
|
||||
if (!in->Next(&in_buffer, &in_len)) {
|
||||
if (in->HadError()) {
|
||||
std::string err = in->GetError();
|
||||
png_error(pngPtr, err.c_str());
|
||||
png_error(png_ptr, err.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
|
||||
memcpy(buffer, inBuffer, bytesRead);
|
||||
if (bytesRead != static_cast<size_t>(inLen)) {
|
||||
in->BackUp(inLen - static_cast<int>(bytesRead));
|
||||
const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
|
||||
memcpy(buffer, in_buffer, bytes_read);
|
||||
if (bytes_read != static_cast<size_t>(in_len)) {
|
||||
in->BackUp(in_len - static_cast<int>(bytes_read));
|
||||
}
|
||||
}
|
||||
|
||||
static void writeDataToStream(png_structp pngPtr, png_bytep buffer,
|
||||
static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
|
||||
png_size_t len) {
|
||||
io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr);
|
||||
io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
|
||||
|
||||
void* outBuffer;
|
||||
int outLen;
|
||||
void* out_buffer;
|
||||
int out_len;
|
||||
while (len > 0) {
|
||||
if (!out->Next(&outBuffer, &outLen)) {
|
||||
if (!out->Next(&out_buffer, &out_len)) {
|
||||
if (out->HadError()) {
|
||||
std::string err = out->GetError();
|
||||
png_error(pngPtr, err.c_str());
|
||||
png_error(png_ptr, err.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
|
||||
memcpy(outBuffer, buffer, bytesWritten);
|
||||
const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
|
||||
memcpy(out_buffer, buffer, bytes_written);
|
||||
|
||||
// Advance the input buffer.
|
||||
buffer += bytesWritten;
|
||||
len -= bytesWritten;
|
||||
buffer += bytes_written;
|
||||
len -= bytes_written;
|
||||
|
||||
// Advance the output buffer.
|
||||
outLen -= static_cast<int>(bytesWritten);
|
||||
out_len -= static_cast<int>(bytes_written);
|
||||
}
|
||||
|
||||
// If the entire output buffer wasn't used, backup.
|
||||
if (outLen > 0) {
|
||||
out->BackUp(outLen);
|
||||
if (out_len > 0) {
|
||||
out->BackUp(out_len);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) {
|
||||
std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) {
|
||||
// Read the first 8 bytes of the file looking for the PNG signature.
|
||||
// Bail early if it does not match.
|
||||
const png_byte* signature;
|
||||
int bufferSize;
|
||||
if (!in->Next((const void**)&signature, &bufferSize)) {
|
||||
context->getDiagnostics()->error(
|
||||
int buffer_size;
|
||||
if (!in->Next((const void**)&signature, &buffer_size)) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << android::base::SystemErrorCodeToString(errno));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(bufferSize) < kPngSignatureSize ||
|
||||
if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
|
||||
png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
|
||||
context->getDiagnostics()->error(
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "file signature does not match PNG signature");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Start at the beginning of the first chunk.
|
||||
in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
|
||||
in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
|
||||
|
||||
// Create and initialize the png_struct with the default error and warning
|
||||
// handlers.
|
||||
// The header version is also passed in to ensure that this was built against
|
||||
// the same
|
||||
// version of libpng.
|
||||
png_structp readPtr =
|
||||
png_structp read_ptr =
|
||||
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (readPtr == nullptr) {
|
||||
context->getDiagnostics()->error(
|
||||
if (read_ptr == nullptr) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "failed to create libpng read png_struct");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create and initialize the memory for image header and data.
|
||||
png_infop infoPtr = png_create_info_struct(readPtr);
|
||||
if (infoPtr == nullptr) {
|
||||
context->getDiagnostics()->error(
|
||||
png_infop info_ptr = png_create_info_struct(read_ptr);
|
||||
if (info_ptr == nullptr) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "failed to create libpng read png_info");
|
||||
png_destroy_read_struct(&readPtr, nullptr, nullptr);
|
||||
png_destroy_read_struct(&read_ptr, nullptr, nullptr);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Automatically release PNG resources at end of scope.
|
||||
PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
|
||||
PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
|
||||
|
||||
// libpng uses longjmp to jump to an error handling routine.
|
||||
// setjmp will only return true if it was jumped to, aka there was
|
||||
// an error.
|
||||
if (setjmp(png_jmpbuf(readPtr))) {
|
||||
if (setjmp(png_jmpbuf(read_ptr))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Handle warnings ourselves via IDiagnostics.
|
||||
png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError,
|
||||
logWarning);
|
||||
png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
|
||||
LogWarning);
|
||||
|
||||
// Set up the read functions which read from our custom data sources.
|
||||
png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream);
|
||||
png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
|
||||
|
||||
// Skip the signature that we already read.
|
||||
png_set_sig_bytes(readPtr, kPngSignatureSize);
|
||||
png_set_sig_bytes(read_ptr, kPngSignatureSize);
|
||||
|
||||
// Read the chunk headers.
|
||||
png_read_info(readPtr, infoPtr);
|
||||
png_read_info(read_ptr, info_ptr);
|
||||
|
||||
// Extract image meta-data from the various chunk headers.
|
||||
uint32_t width, height;
|
||||
int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
|
||||
png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType,
|
||||
&interlaceMethod, &compressionMethod, &filterMethod);
|
||||
int bit_depth, color_type, interlace_method, compression_method,
|
||||
filter_method;
|
||||
png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
|
||||
&interlace_method, &compression_method, &filter_method);
|
||||
|
||||
// When the image is read, expand it so that it is in RGBA 8888 format
|
||||
// so that image handling is uniform.
|
||||
|
||||
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
||||
png_set_palette_to_rgb(readPtr);
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
png_set_palette_to_rgb(read_ptr);
|
||||
}
|
||||
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
|
||||
png_set_expand_gray_1_2_4_to_8(readPtr);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
|
||||
png_set_expand_gray_1_2_4_to_8(read_ptr);
|
||||
}
|
||||
|
||||
if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
|
||||
png_set_tRNS_to_alpha(readPtr);
|
||||
if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
png_set_tRNS_to_alpha(read_ptr);
|
||||
}
|
||||
|
||||
if (bitDepth == 16) {
|
||||
png_set_strip_16(readPtr);
|
||||
if (bit_depth == 16) {
|
||||
png_set_strip_16(read_ptr);
|
||||
}
|
||||
|
||||
if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
|
||||
png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
|
||||
if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
|
||||
png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||
}
|
||||
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY ||
|
||||
colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
png_set_gray_to_rgb(readPtr);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
png_set_gray_to_rgb(read_ptr);
|
||||
}
|
||||
|
||||
if (interlaceMethod != PNG_INTERLACE_NONE) {
|
||||
png_set_interlace_handling(readPtr);
|
||||
if (interlace_method != PNG_INTERLACE_NONE) {
|
||||
png_set_interlace_handling(read_ptr);
|
||||
}
|
||||
|
||||
// Once all the options for reading have been set, we need to flush
|
||||
// them to libpng.
|
||||
png_read_update_info(readPtr, infoPtr);
|
||||
png_read_update_info(read_ptr, info_ptr);
|
||||
|
||||
// 9-patch uses int32_t to index images, so we cap the image dimensions to
|
||||
// something
|
||||
// that can always be represented by 9-patch.
|
||||
if (width > std::numeric_limits<int32_t>::max() ||
|
||||
height > std::numeric_limits<int32_t>::max()) {
|
||||
context->getDiagnostics()->error(DiagMessage()
|
||||
context->GetDiagnostics()->Error(DiagMessage()
|
||||
<< "PNG image dimensions are too large: "
|
||||
<< width << "x" << height);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> outputImage = util::make_unique<Image>();
|
||||
outputImage->width = static_cast<int32_t>(width);
|
||||
outputImage->height = static_cast<int32_t>(height);
|
||||
std::unique_ptr<Image> output_image = util::make_unique<Image>();
|
||||
output_image->width = static_cast<int32_t>(width);
|
||||
output_image->height = static_cast<int32_t>(height);
|
||||
|
||||
const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
|
||||
assert(rowBytes == 4 * width); // RGBA
|
||||
const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
|
||||
CHECK(row_bytes == 4 * width); // RGBA
|
||||
|
||||
// Allocate one large block to hold the image.
|
||||
outputImage->data =
|
||||
std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
|
||||
output_image->data =
|
||||
std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
|
||||
|
||||
// Create an array of rows that index into the data block.
|
||||
outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
|
||||
output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
|
||||
for (uint32_t h = 0; h < height; h++) {
|
||||
outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
|
||||
output_image->rows[h] = output_image->data.get() + (h * row_bytes);
|
||||
}
|
||||
|
||||
// Actually read the image pixels.
|
||||
png_read_image(readPtr, outputImage->rows.get());
|
||||
png_read_image(read_ptr, output_image->rows.get());
|
||||
|
||||
// Finish reading. This will read any other chunks after the image data.
|
||||
png_read_end(readPtr, infoPtr);
|
||||
png_read_end(read_ptr, info_ptr);
|
||||
|
||||
return outputImage;
|
||||
return output_image;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,57 +315,58 @@ constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
|
||||
// - Grayscale + cheap alpha
|
||||
// - Grayscale + alpha
|
||||
//
|
||||
static int pickColorType(int32_t width, int32_t height, bool grayScale,
|
||||
bool convertibleToGrayScale, bool hasNinePatch,
|
||||
size_t colorPaletteSize, size_t alphaPaletteSize) {
|
||||
const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
|
||||
const size_t alphaChunkSize = 16 + alphaPaletteSize;
|
||||
const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
|
||||
const size_t colorDataChunkSize = 16 + 3 * width * height;
|
||||
const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
|
||||
const size_t paletteDataChunkSize = 16 + width * height;
|
||||
static int PickColorType(int32_t width, int32_t height, bool grayscale,
|
||||
bool convertible_to_grayscale, bool has_nine_patch,
|
||||
size_t color_palette_size, size_t alpha_palette_size) {
|
||||
const size_t palette_chunk_size = 16 + color_palette_size * 3;
|
||||
const size_t alpha_chunk_size = 16 + alpha_palette_size;
|
||||
const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
|
||||
const size_t color_data_chunk_size = 16 + 3 * width * height;
|
||||
const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
|
||||
const size_t palette_data_chunk_size = 16 + width * height;
|
||||
|
||||
if (grayScale) {
|
||||
if (alphaPaletteSize == 0) {
|
||||
if (grayscale) {
|
||||
if (alpha_palette_size == 0) {
|
||||
// This is the smallest the data can be.
|
||||
return PNG_COLOR_TYPE_GRAY;
|
||||
} else if (colorPaletteSize <= 256 && !hasNinePatch) {
|
||||
} else if (color_palette_size <= 256 && !has_nine_patch) {
|
||||
// This grayscale has alpha and can fit within a palette.
|
||||
// See if it is worth fitting into a palette.
|
||||
const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
|
||||
paletteDataChunkSize +
|
||||
kPaletteOverheadConstant;
|
||||
if (grayScaleAlphaDataChunkSize > paletteThreshold) {
|
||||
const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
|
||||
palette_data_chunk_size +
|
||||
kPaletteOverheadConstant;
|
||||
if (grayscale_alpha_data_chunk_size > palette_threshold) {
|
||||
return PNG_COLOR_TYPE_PALETTE;
|
||||
}
|
||||
}
|
||||
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
}
|
||||
|
||||
if (colorPaletteSize <= 256 && !hasNinePatch) {
|
||||
if (color_palette_size <= 256 && !has_nine_patch) {
|
||||
// This image can fit inside a palette. Let's see if it is worth it.
|
||||
size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
|
||||
size_t totalSizeWithoutPalette = colorDataChunkSize;
|
||||
if (alphaPaletteSize > 0) {
|
||||
totalSizeWithPalette += alphaPaletteSize;
|
||||
totalSizeWithoutPalette = colorAlphaDataChunkSize;
|
||||
size_t total_size_with_palette =
|
||||
palette_data_chunk_size + palette_chunk_size;
|
||||
size_t total_size_without_palette = color_data_chunk_size;
|
||||
if (alpha_palette_size > 0) {
|
||||
total_size_with_palette += alpha_palette_size;
|
||||
total_size_without_palette = color_alpha_data_chunk_size;
|
||||
}
|
||||
|
||||
if (totalSizeWithoutPalette >
|
||||
totalSizeWithPalette + kPaletteOverheadConstant) {
|
||||
if (total_size_without_palette >
|
||||
total_size_with_palette + kPaletteOverheadConstant) {
|
||||
return PNG_COLOR_TYPE_PALETTE;
|
||||
}
|
||||
}
|
||||
|
||||
if (convertibleToGrayScale) {
|
||||
if (alphaPaletteSize == 0) {
|
||||
if (convertible_to_grayscale) {
|
||||
if (alpha_palette_size == 0) {
|
||||
return PNG_COLOR_TYPE_GRAY;
|
||||
} else {
|
||||
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
}
|
||||
}
|
||||
|
||||
if (alphaPaletteSize == 0) {
|
||||
if (alpha_palette_size == 0) {
|
||||
return PNG_COLOR_TYPE_RGB;
|
||||
}
|
||||
return PNG_COLOR_TYPE_RGBA;
|
||||
@@ -371,11 +378,11 @@ static int pickColorType(int32_t width, int32_t height, bool grayScale,
|
||||
// This must be done before writing image data.
|
||||
// Image data must be transformed to use the indices assigned within the
|
||||
// palette.
|
||||
static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
|
||||
std::unordered_map<uint32_t, int>* colorPalette,
|
||||
std::unordered_set<uint32_t>* alphaPalette) {
|
||||
assert(colorPalette->size() <= 256);
|
||||
assert(alphaPalette->size() <= 256);
|
||||
static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
|
||||
std::unordered_map<uint32_t, int>* color_palette,
|
||||
std::unordered_set<uint32_t>* alpha_palette) {
|
||||
CHECK(color_palette->size() <= 256);
|
||||
CHECK(alpha_palette->size() <= 256);
|
||||
|
||||
// Populate the PNG palette struct and assign indices to the color
|
||||
// palette.
|
||||
@@ -384,160 +391,161 @@ static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
|
||||
// This will ensure that we can truncate the alpha palette if it is
|
||||
// smaller than the color palette.
|
||||
int index = 0;
|
||||
for (uint32_t color : *alphaPalette) {
|
||||
(*colorPalette)[color] = index++;
|
||||
for (uint32_t color : *alpha_palette) {
|
||||
(*color_palette)[color] = index++;
|
||||
}
|
||||
|
||||
// Assign the rest of the entries.
|
||||
for (auto& entry : *colorPalette) {
|
||||
for (auto& entry : *color_palette) {
|
||||
if (entry.second == -1) {
|
||||
entry.second = index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the PNG color palette struct.
|
||||
auto colorPaletteBytes =
|
||||
std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
|
||||
auto color_palette_bytes =
|
||||
std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
|
||||
|
||||
std::unique_ptr<png_byte[]> alphaPaletteBytes;
|
||||
if (!alphaPalette->empty()) {
|
||||
alphaPaletteBytes =
|
||||
std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
|
||||
std::unique_ptr<png_byte[]> alpha_palette_bytes;
|
||||
if (!alpha_palette->empty()) {
|
||||
alpha_palette_bytes =
|
||||
std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
|
||||
}
|
||||
|
||||
for (const auto& entry : *colorPalette) {
|
||||
for (const auto& entry : *color_palette) {
|
||||
const uint32_t color = entry.first;
|
||||
const int index = entry.second;
|
||||
assert(index >= 0);
|
||||
assert(static_cast<size_t>(index) < colorPalette->size());
|
||||
CHECK(index >= 0);
|
||||
CHECK(static_cast<size_t>(index) < color_palette->size());
|
||||
|
||||
png_colorp slot = colorPaletteBytes.get() + index;
|
||||
png_colorp slot = color_palette_bytes.get() + index;
|
||||
slot->red = color >> 24;
|
||||
slot->green = color >> 16;
|
||||
slot->blue = color >> 8;
|
||||
|
||||
const png_byte alpha = color & 0x000000ff;
|
||||
if (alpha != 0xff && alphaPaletteBytes) {
|
||||
assert(static_cast<size_t>(index) < alphaPalette->size());
|
||||
alphaPaletteBytes[index] = alpha;
|
||||
if (alpha != 0xff && alpha_palette_bytes) {
|
||||
CHECK(static_cast<size_t>(index) < alpha_palette->size());
|
||||
alpha_palette_bytes[index] = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
// The bytes get copied here, so it is safe to release colorPaletteBytes at
|
||||
// The bytes get copied here, so it is safe to release color_palette_bytes at
|
||||
// the end of function
|
||||
// scope.
|
||||
png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(),
|
||||
colorPalette->size());
|
||||
png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
|
||||
color_palette->size());
|
||||
|
||||
if (alphaPaletteBytes) {
|
||||
png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(),
|
||||
alphaPalette->size(), nullptr);
|
||||
if (alpha_palette_bytes) {
|
||||
png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
|
||||
alpha_palette->size(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before
|
||||
// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
|
||||
// before
|
||||
// writing image data.
|
||||
static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr,
|
||||
const NinePatch* ninePatch) {
|
||||
static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
|
||||
const NinePatch* nine_patch) {
|
||||
// The order of the chunks is important.
|
||||
// 9-patch code in older platforms expects the 9-patch chunk to
|
||||
// be last.
|
||||
|
||||
png_unknown_chunk unknownChunks[3];
|
||||
memset(unknownChunks, 0, sizeof(unknownChunks));
|
||||
png_unknown_chunk unknown_chunks[3];
|
||||
memset(unknown_chunks, 0, sizeof(unknown_chunks));
|
||||
|
||||
size_t index = 0;
|
||||
size_t chunkLen = 0;
|
||||
size_t chunk_len = 0;
|
||||
|
||||
std::unique_ptr<uint8_t[]> serializedOutline =
|
||||
ninePatch->serializeRoundedRectOutline(&chunkLen);
|
||||
strcpy((char*)unknownChunks[index].name, "npOl");
|
||||
unknownChunks[index].size = chunkLen;
|
||||
unknownChunks[index].data = (png_bytep)serializedOutline.get();
|
||||
unknownChunks[index].location = PNG_HAVE_PLTE;
|
||||
std::unique_ptr<uint8_t[]> serialized_outline =
|
||||
nine_patch->SerializeRoundedRectOutline(&chunk_len);
|
||||
strcpy((char*)unknown_chunks[index].name, "npOl");
|
||||
unknown_chunks[index].size = chunk_len;
|
||||
unknown_chunks[index].data = (png_bytep)serialized_outline.get();
|
||||
unknown_chunks[index].location = PNG_HAVE_PLTE;
|
||||
index++;
|
||||
|
||||
std::unique_ptr<uint8_t[]> serializedLayoutBounds;
|
||||
if (ninePatch->layoutBounds.nonZero()) {
|
||||
serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
|
||||
strcpy((char*)unknownChunks[index].name, "npLb");
|
||||
unknownChunks[index].size = chunkLen;
|
||||
unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get();
|
||||
unknownChunks[index].location = PNG_HAVE_PLTE;
|
||||
std::unique_ptr<uint8_t[]> serialized_layout_bounds;
|
||||
if (nine_patch->layout_bounds.nonZero()) {
|
||||
serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
|
||||
strcpy((char*)unknown_chunks[index].name, "npLb");
|
||||
unknown_chunks[index].size = chunk_len;
|
||||
unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
|
||||
unknown_chunks[index].location = PNG_HAVE_PLTE;
|
||||
index++;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> serializedNinePatch =
|
||||
ninePatch->serializeBase(&chunkLen);
|
||||
strcpy((char*)unknownChunks[index].name, "npTc");
|
||||
unknownChunks[index].size = chunkLen;
|
||||
unknownChunks[index].data = (png_bytep)serializedNinePatch.get();
|
||||
unknownChunks[index].location = PNG_HAVE_PLTE;
|
||||
std::unique_ptr<uint8_t[]> serialized_nine_patch =
|
||||
nine_patch->SerializeBase(&chunk_len);
|
||||
strcpy((char*)unknown_chunks[index].name, "npTc");
|
||||
unknown_chunks[index].size = chunk_len;
|
||||
unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
|
||||
unknown_chunks[index].location = PNG_HAVE_PLTE;
|
||||
index++;
|
||||
|
||||
// Handle all unknown chunks. We are manually setting the chunks here,
|
||||
// so we will only ever handle our custom chunks.
|
||||
png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
|
||||
png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
|
||||
|
||||
// Set the actual chunks here. The data gets copied, so our buffers can
|
||||
// safely go out of scope.
|
||||
png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
|
||||
png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
|
||||
}
|
||||
|
||||
bool writePng(IAaptContext* context, const Image* image,
|
||||
const NinePatch* ninePatch, io::OutputStream* out,
|
||||
bool WritePng(IAaptContext* context, const Image* image,
|
||||
const NinePatch* nine_patch, io::OutputStream* out,
|
||||
const PngOptions& options) {
|
||||
// Create and initialize the write png_struct with the default error and
|
||||
// warning handlers.
|
||||
// The header version is also passed in to ensure that this was built against
|
||||
// the same
|
||||
// version of libpng.
|
||||
png_structp writePtr =
|
||||
png_structp write_ptr =
|
||||
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (writePtr == nullptr) {
|
||||
context->getDiagnostics()->error(
|
||||
if (write_ptr == nullptr) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "failed to create libpng write png_struct");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate memory to store image header data.
|
||||
png_infop writeInfoPtr = png_create_info_struct(writePtr);
|
||||
if (writeInfoPtr == nullptr) {
|
||||
context->getDiagnostics()->error(
|
||||
png_infop write_info_ptr = png_create_info_struct(write_ptr);
|
||||
if (write_info_ptr == nullptr) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage() << "failed to create libpng write png_info");
|
||||
png_destroy_write_struct(&writePtr, nullptr);
|
||||
png_destroy_write_struct(&write_ptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Automatically release PNG resources at end of scope.
|
||||
PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
|
||||
PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
|
||||
|
||||
// libpng uses longjmp to jump to error handling routines.
|
||||
// setjmp will return true only if it was jumped to, aka, there was an error.
|
||||
if (setjmp(png_jmpbuf(writePtr))) {
|
||||
if (setjmp(png_jmpbuf(write_ptr))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle warnings with our IDiagnostics.
|
||||
png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError,
|
||||
logWarning);
|
||||
png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
|
||||
LogWarning);
|
||||
|
||||
// Set up the write functions which write to our custom data sources.
|
||||
png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr);
|
||||
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
|
||||
|
||||
// We want small files and can take the performance hit to achieve this goal.
|
||||
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
|
||||
png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
|
||||
|
||||
// Begin analysis of the image data.
|
||||
// Scan the entire image and determine if:
|
||||
// 1. Every pixel has R == G == B (grayscale)
|
||||
// 2. Every pixel has A == 255 (opaque)
|
||||
// 3. There are no more than 256 distinct RGBA colors (palette).
|
||||
std::unordered_map<uint32_t, int> colorPalette;
|
||||
std::unordered_set<uint32_t> alphaPalette;
|
||||
bool needsToZeroRGBChannelsOfTransparentPixels = false;
|
||||
bool grayScale = true;
|
||||
int maxGrayDeviation = 0;
|
||||
std::unordered_map<uint32_t, int> color_palette;
|
||||
std::unordered_set<uint32_t> alpha_palette;
|
||||
bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
|
||||
bool grayscale = true;
|
||||
int max_gray_deviation = 0;
|
||||
|
||||
for (int32_t y = 0; y < image->height; y++) {
|
||||
const uint8_t* row = image->rows[y];
|
||||
@@ -551,60 +559,60 @@ bool writePng(IAaptContext* context, const Image* image,
|
||||
// The color is completely transparent.
|
||||
// For purposes of palettes and grayscale optimization,
|
||||
// treat all channels as 0x00.
|
||||
needsToZeroRGBChannelsOfTransparentPixels =
|
||||
needsToZeroRGBChannelsOfTransparentPixels ||
|
||||
needs_to_zero_rgb_channels_of_transparent_pixels =
|
||||
needs_to_zero_rgb_channels_of_transparent_pixels ||
|
||||
(red != 0 || green != 0 || blue != 0);
|
||||
red = green = blue = 0;
|
||||
}
|
||||
|
||||
// Insert the color into the color palette.
|
||||
const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
|
||||
colorPalette[color] = -1;
|
||||
color_palette[color] = -1;
|
||||
|
||||
// If the pixel has non-opaque alpha, insert it into the
|
||||
// alpha palette.
|
||||
if (alpha != 0xff) {
|
||||
alphaPalette.insert(color);
|
||||
alpha_palette.insert(color);
|
||||
}
|
||||
|
||||
// Check if the image is indeed grayscale.
|
||||
if (grayScale) {
|
||||
if (grayscale) {
|
||||
if (red != green || red != blue) {
|
||||
grayScale = false;
|
||||
grayscale = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the gray scale deviation so that it can be compared
|
||||
// with the threshold.
|
||||
maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
|
||||
maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
|
||||
maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
|
||||
max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
|
||||
max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
|
||||
max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
|
||||
}
|
||||
}
|
||||
|
||||
if (context->verbose()) {
|
||||
if (context->IsVerbose()) {
|
||||
DiagMessage msg;
|
||||
msg << " paletteSize=" << colorPalette.size()
|
||||
<< " alphaPaletteSize=" << alphaPalette.size()
|
||||
<< " maxGrayDeviation=" << maxGrayDeviation
|
||||
<< " grayScale=" << (grayScale ? "true" : "false");
|
||||
context->getDiagnostics()->note(msg);
|
||||
msg << " paletteSize=" << color_palette.size()
|
||||
<< " alphaPaletteSize=" << alpha_palette.size()
|
||||
<< " maxGrayDeviation=" << max_gray_deviation
|
||||
<< " grayScale=" << (grayscale ? "true" : "false");
|
||||
context->GetDiagnostics()->Note(msg);
|
||||
}
|
||||
|
||||
const bool convertibleToGrayScale =
|
||||
maxGrayDeviation <= options.grayScaleTolerance;
|
||||
const bool convertible_to_grayscale =
|
||||
max_gray_deviation <= options.grayscale_tolerance;
|
||||
|
||||
const int newColorType = pickColorType(
|
||||
image->width, image->height, grayScale, convertibleToGrayScale,
|
||||
ninePatch != nullptr, colorPalette.size(), alphaPalette.size());
|
||||
const int new_color_type = PickColorType(
|
||||
image->width, image->height, grayscale, convertible_to_grayscale,
|
||||
nine_patch != nullptr, color_palette.size(), alpha_palette.size());
|
||||
|
||||
if (context->verbose()) {
|
||||
if (context->IsVerbose()) {
|
||||
DiagMessage msg;
|
||||
msg << "encoding PNG ";
|
||||
if (ninePatch) {
|
||||
if (nine_patch) {
|
||||
msg << "(with 9-patch) as ";
|
||||
}
|
||||
switch (newColorType) {
|
||||
switch (new_color_type) {
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
msg << "GRAY";
|
||||
break;
|
||||
@@ -621,137 +629,138 @@ bool writePng(IAaptContext* context, const Image* image,
|
||||
msg << "PALETTE";
|
||||
break;
|
||||
default:
|
||||
msg << "unknown type " << newColorType;
|
||||
msg << "unknown type " << new_color_type;
|
||||
break;
|
||||
}
|
||||
context->getDiagnostics()->note(msg);
|
||||
context->GetDiagnostics()->Note(msg);
|
||||
}
|
||||
|
||||
png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8,
|
||||
newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
|
||||
new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
if (newColorType & PNG_COLOR_MASK_PALETTE) {
|
||||
if (new_color_type & PNG_COLOR_MASK_PALETTE) {
|
||||
// Assigns indices to the palette, and writes the encoded palette to the
|
||||
// libpng writePtr.
|
||||
writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
|
||||
png_set_filter(writePtr, 0, PNG_NO_FILTERS);
|
||||
WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
|
||||
png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
|
||||
} else {
|
||||
png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
|
||||
png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
|
||||
}
|
||||
|
||||
if (ninePatch) {
|
||||
writeNinePatch(writePtr, writeInfoPtr, ninePatch);
|
||||
if (nine_patch) {
|
||||
WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
|
||||
}
|
||||
|
||||
// Flush our updates to the header.
|
||||
png_write_info(writePtr, writeInfoPtr);
|
||||
png_write_info(write_ptr, write_info_ptr);
|
||||
|
||||
// Write out each row of image data according to its encoding.
|
||||
if (newColorType == PNG_COLOR_TYPE_PALETTE) {
|
||||
if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
// 1 byte/pixel.
|
||||
auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
|
||||
auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
|
||||
|
||||
for (int32_t y = 0; y < image->height; y++) {
|
||||
png_const_bytep inRow = image->rows[y];
|
||||
png_const_bytep in_row = image->rows[y];
|
||||
for (int32_t x = 0; x < image->width; x++) {
|
||||
int rr = *inRow++;
|
||||
int gg = *inRow++;
|
||||
int bb = *inRow++;
|
||||
int aa = *inRow++;
|
||||
int rr = *in_row++;
|
||||
int gg = *in_row++;
|
||||
int bb = *in_row++;
|
||||
int aa = *in_row++;
|
||||
if (aa == 0) {
|
||||
// Zero out color channels when transparent.
|
||||
rr = gg = bb = 0;
|
||||
}
|
||||
|
||||
const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
|
||||
const int idx = colorPalette[color];
|
||||
assert(idx != -1);
|
||||
outRow[x] = static_cast<png_byte>(idx);
|
||||
const int idx = color_palette[color];
|
||||
CHECK(idx != -1);
|
||||
out_row[x] = static_cast<png_byte>(idx);
|
||||
}
|
||||
png_write_row(writePtr, outRow.get());
|
||||
png_write_row(write_ptr, out_row.get());
|
||||
}
|
||||
} else if (newColorType == PNG_COLOR_TYPE_GRAY ||
|
||||
newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
|
||||
auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
|
||||
} else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
|
||||
auto out_row =
|
||||
std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
|
||||
|
||||
for (int32_t y = 0; y < image->height; y++) {
|
||||
png_const_bytep inRow = image->rows[y];
|
||||
png_const_bytep in_row = image->rows[y];
|
||||
for (int32_t x = 0; x < image->width; x++) {
|
||||
int rr = inRow[x * 4];
|
||||
int gg = inRow[x * 4 + 1];
|
||||
int bb = inRow[x * 4 + 2];
|
||||
int aa = inRow[x * 4 + 3];
|
||||
int rr = in_row[x * 4];
|
||||
int gg = in_row[x * 4 + 1];
|
||||
int bb = in_row[x * 4 + 2];
|
||||
int aa = in_row[x * 4 + 3];
|
||||
if (aa == 0) {
|
||||
// Zero out the gray channel when transparent.
|
||||
rr = gg = bb = 0;
|
||||
}
|
||||
|
||||
if (grayScale) {
|
||||
if (grayscale) {
|
||||
// The image was already grayscale, red == green == blue.
|
||||
outRow[x * bpp] = inRow[x * 4];
|
||||
out_row[x * bpp] = in_row[x * 4];
|
||||
} else {
|
||||
// The image is convertible to grayscale, use linear-luminance of
|
||||
// sRGB colorspace:
|
||||
// https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
|
||||
outRow[x * bpp] =
|
||||
out_row[x * bpp] =
|
||||
(png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
// Write out alpha if we have it.
|
||||
outRow[x * bpp + 1] = aa;
|
||||
out_row[x * bpp + 1] = aa;
|
||||
}
|
||||
}
|
||||
png_write_row(writePtr, outRow.get());
|
||||
png_write_row(write_ptr, out_row.get());
|
||||
}
|
||||
} else if (newColorType == PNG_COLOR_TYPE_RGB ||
|
||||
newColorType == PNG_COLOR_TYPE_RGBA) {
|
||||
const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
|
||||
if (needsToZeroRGBChannelsOfTransparentPixels) {
|
||||
} else if (new_color_type == PNG_COLOR_TYPE_RGB ||
|
||||
new_color_type == PNG_COLOR_TYPE_RGBA) {
|
||||
const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
|
||||
if (needs_to_zero_rgb_channels_of_transparent_pixels) {
|
||||
// The source RGBA data can't be used as-is, because we need to zero out
|
||||
// the RGB
|
||||
// values of transparent pixels.
|
||||
auto outRow =
|
||||
auto out_row =
|
||||
std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
|
||||
|
||||
for (int32_t y = 0; y < image->height; y++) {
|
||||
png_const_bytep inRow = image->rows[y];
|
||||
png_const_bytep in_row = image->rows[y];
|
||||
for (int32_t x = 0; x < image->width; x++) {
|
||||
int rr = *inRow++;
|
||||
int gg = *inRow++;
|
||||
int bb = *inRow++;
|
||||
int aa = *inRow++;
|
||||
int rr = *in_row++;
|
||||
int gg = *in_row++;
|
||||
int bb = *in_row++;
|
||||
int aa = *in_row++;
|
||||
if (aa == 0) {
|
||||
// Zero out the RGB channels when transparent.
|
||||
rr = gg = bb = 0;
|
||||
}
|
||||
outRow[x * bpp] = rr;
|
||||
outRow[x * bpp + 1] = gg;
|
||||
outRow[x * bpp + 2] = bb;
|
||||
out_row[x * bpp] = rr;
|
||||
out_row[x * bpp + 1] = gg;
|
||||
out_row[x * bpp + 2] = bb;
|
||||
if (bpp == 4) {
|
||||
outRow[x * bpp + 3] = aa;
|
||||
out_row[x * bpp + 3] = aa;
|
||||
}
|
||||
}
|
||||
png_write_row(writePtr, outRow.get());
|
||||
png_write_row(write_ptr, out_row.get());
|
||||
}
|
||||
} else {
|
||||
// The source image can be used as-is, just tell libpng whether or not to
|
||||
// ignore
|
||||
// the alpha channel.
|
||||
if (newColorType == PNG_COLOR_TYPE_RGB) {
|
||||
if (new_color_type == PNG_COLOR_TYPE_RGB) {
|
||||
// Delete the extraneous alpha values that we appended to our buffer
|
||||
// when reading the original values.
|
||||
png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
|
||||
png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
|
||||
}
|
||||
png_write_image(writePtr, image->rows.get());
|
||||
png_write_image(write_ptr, image->rows.get());
|
||||
}
|
||||
} else {
|
||||
assert(false && "unreachable");
|
||||
LOG(FATAL) << "unreachable";
|
||||
}
|
||||
|
||||
png_write_end(writePtr, writeInfoPtr);
|
||||
png_write_end(write_ptr, write_info_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,27 +15,29 @@
|
||||
*/
|
||||
|
||||
#include "compile/PseudolocaleGenerator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "compile/Pseudolocalizer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
std::unique_ptr<StyledString> PseudolocalizeStyledString(
|
||||
StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
|
||||
Pseudolocalizer localizer(method);
|
||||
|
||||
const StringPiece originalText = *string->value->str;
|
||||
const StringPiece original_text = *string->value->str;
|
||||
|
||||
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});
|
||||
localized.spans.push_back(
|
||||
Span{*span.name, span.first_char, span.last_char});
|
||||
}
|
||||
|
||||
// The ranges are all represented with a single value. This is the start of
|
||||
@@ -49,8 +51,8 @@ std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
// 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;
|
||||
uint32_t* update_start;
|
||||
uint32_t* update_end;
|
||||
};
|
||||
|
||||
auto cmp = [](const Range& r, size_t index) -> bool {
|
||||
@@ -64,109 +66,113 @@ std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
//
|
||||
std::vector<Range> ranges;
|
||||
ranges.push_back(Range{0});
|
||||
ranges.push_back(Range{originalText.size() - 1});
|
||||
ranges.push_back(Range{original_text.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;
|
||||
std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.first_char) {
|
||||
iter->update_start = &localized.spans[i].first_char;
|
||||
} else {
|
||||
ranges.insert(
|
||||
iter, Range{span.firstChar, &localized.spans[i].firstChar, nullptr});
|
||||
ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char,
|
||||
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;
|
||||
iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp);
|
||||
if (iter != ranges.end() && iter->start == span.last_char) {
|
||||
iter->update_end = &localized.spans[i].last_char;
|
||||
} else {
|
||||
ranges.insert(
|
||||
iter, Range{span.lastChar, nullptr, &localized.spans[i].lastChar});
|
||||
iter, Range{span.last_char, nullptr, &localized.spans[i].last_char});
|
||||
}
|
||||
}
|
||||
|
||||
localized.str += localizer.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;
|
||||
size_t len = original_text.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].update_start) {
|
||||
*ranges[i].update_start = localized.str.size();
|
||||
}
|
||||
|
||||
if (ranges[i].updateEnd) {
|
||||
*ranges[i].updateEnd = localized.str.size();
|
||||
if (ranges[i].update_end) {
|
||||
*ranges[i].update_end = localized.str.size();
|
||||
}
|
||||
|
||||
localized.str += localizer.text(originalText.substr(start, len));
|
||||
localized.str += localizer.Text(original_text.substr(start, len));
|
||||
}
|
||||
|
||||
localized.str += localizer.end();
|
||||
localized.str += localizer.End();
|
||||
|
||||
std::unique_ptr<StyledString> localizedString =
|
||||
util::make_unique<StyledString>(pool->makeRef(localized));
|
||||
localizedString->setSource(string->getSource());
|
||||
return localizedString;
|
||||
std::unique_ptr<StyledString> localized_string =
|
||||
util::make_unique<StyledString>(pool->MakeRef(localized));
|
||||
localized_string->SetSource(string->GetSource());
|
||||
return localized_string;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Visitor : public RawValueVisitor {
|
||||
StringPool* mPool;
|
||||
Pseudolocalizer::Method mMethod;
|
||||
Pseudolocalizer mLocalizer;
|
||||
|
||||
class Visitor : public RawValueVisitor {
|
||||
public:
|
||||
// Either value or item will be populated upon visiting the value.
|
||||
std::unique_ptr<Value> mValue;
|
||||
std::unique_ptr<Item> mItem;
|
||||
std::unique_ptr<Value> value;
|
||||
std::unique_ptr<Item> item;
|
||||
|
||||
Visitor(StringPool* pool, Pseudolocalizer::Method method)
|
||||
: mPool(pool), mMethod(method), mLocalizer(method) {}
|
||||
: pool_(pool), method_(method), localizer_(method) {}
|
||||
|
||||
void visit(Plural* plural) override {
|
||||
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);
|
||||
Visitor sub_visitor(pool_, method_);
|
||||
if (plural->values[i]) {
|
||||
plural->values[i]->accept(&subVisitor);
|
||||
if (subVisitor.mValue) {
|
||||
localized->values[i] = std::move(subVisitor.mItem);
|
||||
plural->values[i]->Accept(&sub_visitor);
|
||||
if (sub_visitor.value) {
|
||||
localized->values[i] = std::move(sub_visitor.item);
|
||||
} else {
|
||||
localized->values[i] =
|
||||
std::unique_ptr<Item>(plural->values[i]->clone(mPool));
|
||||
std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
|
||||
}
|
||||
}
|
||||
}
|
||||
localized->setSource(plural->getSource());
|
||||
localized->setWeak(true);
|
||||
mValue = std::move(localized);
|
||||
localized->SetSource(plural->GetSource());
|
||||
localized->SetWeak(true);
|
||||
value = std::move(localized);
|
||||
}
|
||||
|
||||
void visit(String* string) override {
|
||||
void Visit(String* string) override {
|
||||
std::string result =
|
||||
mLocalizer.start() + mLocalizer.text(*string->value) + mLocalizer.end();
|
||||
localizer_.Start() + localizer_.Text(*string->value) + localizer_.End();
|
||||
std::unique_ptr<String> localized =
|
||||
util::make_unique<String>(mPool->makeRef(result));
|
||||
localized->setSource(string->getSource());
|
||||
localized->setWeak(true);
|
||||
mItem = std::move(localized);
|
||||
util::make_unique<String>(pool_->MakeRef(result));
|
||||
localized->SetSource(string->GetSource());
|
||||
localized->SetWeak(true);
|
||||
item = std::move(localized);
|
||||
}
|
||||
|
||||
void visit(StyledString* string) override {
|
||||
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
|
||||
mItem->setWeak(true);
|
||||
void Visit(StyledString* string) override {
|
||||
item = PseudolocalizeStyledString(string, method_, pool_);
|
||||
item->SetWeak(true);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(Visitor);
|
||||
|
||||
StringPool* pool_;
|
||||
Pseudolocalizer::Method method_;
|
||||
Pseudolocalizer localizer_;
|
||||
};
|
||||
|
||||
ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
|
||||
ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
|
||||
Pseudolocalizer::Method m) {
|
||||
ConfigDescription modified = base;
|
||||
switch (m) {
|
||||
@@ -189,31 +195,31 @@ ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
|
||||
return modified;
|
||||
}
|
||||
|
||||
void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
|
||||
ResourceConfigValue* originalValue,
|
||||
void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
|
||||
ResourceConfigValue* original_value,
|
||||
StringPool* pool, ResourceEntry* entry) {
|
||||
Visitor visitor(pool, method);
|
||||
originalValue->value->accept(&visitor);
|
||||
original_value->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> localized_value;
|
||||
if (visitor.value) {
|
||||
localized_value = std::move(visitor.value);
|
||||
} else if (visitor.item) {
|
||||
localized_value = std::move(visitor.item);
|
||||
}
|
||||
|
||||
if (!localizedValue) {
|
||||
if (!localized_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigDescription configWithAccent =
|
||||
modifyConfigForPseudoLocale(originalValue->config, method);
|
||||
ConfigDescription config_with_accent =
|
||||
ModifyConfigForPseudoLocale(original_value->config, method);
|
||||
|
||||
ResourceConfigValue* newConfigValue =
|
||||
entry->findOrCreateValue(configWithAccent, originalValue->product);
|
||||
if (!newConfigValue->value) {
|
||||
ResourceConfigValue* new_config_value =
|
||||
entry->FindOrCreateValue(config_with_accent, original_value->product);
|
||||
if (!new_config_value->value) {
|
||||
// Only use auto-generated pseudo-localization if none is defined.
|
||||
newConfigValue->value = std::move(localizedValue);
|
||||
new_config_value->value = std::move(localized_value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,29 +228,30 @@ void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
|
||||
* default locale)
|
||||
* and is translateable.
|
||||
*/
|
||||
static bool isPseudolocalizable(ResourceConfigValue* configValue) {
|
||||
const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
|
||||
static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
|
||||
const int diff =
|
||||
config_value->config.diff(ConfigDescription::DefaultConfig());
|
||||
if (diff & ConfigDescription::CONFIG_LOCALE) {
|
||||
return false;
|
||||
}
|
||||
return configValue->value->isTranslateable();
|
||||
return config_value->value->IsTranslateable();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool PseudolocaleGenerator::consume(IAaptContext* context,
|
||||
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);
|
||||
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());
|
||||
PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
|
||||
&table->string_pool, entry.get());
|
||||
PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
|
||||
&table->string_pool, entry.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
std::unique_ptr<StyledString> pseudolocalizeStyledString(
|
||||
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
|
||||
|
||||
@@ -15,118 +15,119 @@
|
||||
*/
|
||||
|
||||
#include "compile/PseudolocaleGenerator.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
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}};
|
||||
StyleString original_style;
|
||||
original_style.str = "Hello world!";
|
||||
original_style.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(),
|
||||
std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
|
||||
Pseudolocalizer::Method::kNone, &pool);
|
||||
|
||||
EXPECT_EQ(originalStyle.str, *newString->value->str);
|
||||
ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
|
||||
EXPECT_EQ(original_style.str, *new_string->value->str);
|
||||
ASSERT_EQ(original_style.spans.size(), new_string->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(), new_string->value->spans[0].first_char);
|
||||
EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char);
|
||||
EXPECT_EQ(std::string("b"), *new_string->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(),
|
||||
new_string->value->spans[1].first_char);
|
||||
EXPECT_EQ(std::string("Hello w").size(),
|
||||
new_string->value->spans[1].last_char);
|
||||
EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name);
|
||||
|
||||
EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char);
|
||||
EXPECT_EQ(std::string("Hello worl").size(),
|
||||
newString->value->spans[2].lastChar);
|
||||
EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
|
||||
new_string->value->spans[2].last_char);
|
||||
EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
|
||||
|
||||
originalStyle.spans.push_back(Span{"em", 0, 11u});
|
||||
original_style.spans.push_back(Span{"em", 0, 11u});
|
||||
|
||||
newString = pseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
|
||||
new_string = PseudolocalizeStyledString(
|
||||
util::make_unique<StyledString>(pool.MakeRef(original_style)).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]"), *new_string->value->str);
|
||||
ASSERT_EQ(original_style.spans.size(), new_string->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(), new_string->value->spans[0].first_char);
|
||||
EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char);
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ").size(),
|
||||
newString->value->spans[1].firstChar);
|
||||
new_string->value->spans[1].first_char);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
|
||||
newString->value->spans[1].lastChar);
|
||||
new_string->value->spans[1].last_char);
|
||||
|
||||
EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
|
||||
EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
|
||||
newString->value->spans[2].lastChar);
|
||||
new_string->value->spans[2].last_char);
|
||||
|
||||
EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
|
||||
EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char);
|
||||
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
|
||||
newString->value->spans[3].lastChar);
|
||||
new_string->value->spans[3].last_char);
|
||||
}
|
||||
|
||||
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();
|
||||
.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");
|
||||
String* val = test::GetValue<String>(table.get(), "android:string/four");
|
||||
ASSERT_NE(nullptr, val);
|
||||
val->setTranslateable(false);
|
||||
val->SetTranslateable(false);
|
||||
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
PseudolocaleGenerator generator;
|
||||
ASSERT_TRUE(generator.consume(context.get(), table.get()));
|
||||
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")));
|
||||
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")));
|
||||
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")));
|
||||
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")));
|
||||
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"));
|
||||
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")));
|
||||
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")));
|
||||
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")));
|
||||
test::GetValueForConfig<String>(table.get(), "android:string/four",
|
||||
test::ParseConfigOrDie("ar-rXB")));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,77 +15,78 @@
|
||||
*/
|
||||
|
||||
#include "compile/Pseudolocalizer.h"
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// String basis to generate expansion
|
||||
static const std::string k_expansion_string =
|
||||
static const std::string kExpansionString =
|
||||
"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";
|
||||
static const std::string k_rlo = "\u202e";
|
||||
static const std::string k_pdf = "\u202c";
|
||||
static const std::string kRlm = "\u200f";
|
||||
static const std::string kRlo = "\u202e";
|
||||
static const std::string kPdf = "\u202c";
|
||||
|
||||
// Placeholder marks
|
||||
static const std::string k_placeholder_open = "\u00bb";
|
||||
static const std::string k_placeholder_close = "\u00ab";
|
||||
static const std::string kPlaceholderOpen = "\u00bb";
|
||||
static const std::string kPlaceholderClose = "\u00ab";
|
||||
|
||||
static const char k_arg_start = '{';
|
||||
static const char k_arg_end = '}';
|
||||
static const char kArgStart = '{';
|
||||
static const char kArgEnd = '}';
|
||||
|
||||
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();
|
||||
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;
|
||||
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;
|
||||
PseudoMethodAccent() : depth_(0), word_count_(0), length_(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;
|
||||
size_t depth_;
|
||||
size_t word_count_;
|
||||
size_t length_;
|
||||
};
|
||||
|
||||
Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
|
||||
setMethod(method);
|
||||
Pseudolocalizer::Pseudolocalizer(Method method) : last_depth_(0) {
|
||||
SetMethod(method);
|
||||
}
|
||||
|
||||
void Pseudolocalizer::setMethod(Method method) {
|
||||
void Pseudolocalizer::SetMethod(Method method) {
|
||||
switch (method) {
|
||||
case Method::kNone:
|
||||
mImpl = util::make_unique<PseudoMethodNone>();
|
||||
impl_ = util::make_unique<PseudoMethodNone>();
|
||||
break;
|
||||
case Method::kAccent:
|
||||
mImpl = util::make_unique<PseudoMethodAccent>();
|
||||
impl_ = util::make_unique<PseudoMethodAccent>();
|
||||
break;
|
||||
case Method::kBidi:
|
||||
mImpl = util::make_unique<PseudoMethodBidi>();
|
||||
impl_ = util::make_unique<PseudoMethodBidi>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Pseudolocalizer::text(const StringPiece& text) {
|
||||
std::string Pseudolocalizer::Text(const StringPiece& text) {
|
||||
std::string out;
|
||||
size_t depth = mLastDepth;
|
||||
size_t depth = last_depth_;
|
||||
size_t lastpos, pos;
|
||||
const size_t length = text.size();
|
||||
const char* str = text.data();
|
||||
@@ -101,42 +102,41 @@ std::string Pseudolocalizer::text(const StringPiece& text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == k_arg_start) {
|
||||
if (c == kArgStart) {
|
||||
depth++;
|
||||
} else if (c == k_arg_end && depth) {
|
||||
} else if (c == kArgEnd && depth) {
|
||||
depth--;
|
||||
}
|
||||
|
||||
if (mLastDepth != depth || pos == length - 1) {
|
||||
bool pseudo = ((mLastDepth % 2) == 0);
|
||||
if (last_depth_ != depth || pos == length - 1) {
|
||||
bool pseudo = ((last_depth_ % 2) == 0);
|
||||
size_t nextpos = pos;
|
||||
if (!pseudo || depth == mLastDepth) {
|
||||
if (!pseudo || depth == last_depth_) {
|
||||
nextpos++;
|
||||
}
|
||||
size_t size = nextpos - lastpos;
|
||||
if (size) {
|
||||
std::string chunk = text.substr(lastpos, size).toString();
|
||||
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);
|
||||
chunk = impl_->Text(chunk);
|
||||
} else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
|
||||
chunk = impl_->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());
|
||||
if (pseudo && depth < last_depth_) { // End of message
|
||||
out.append(impl_->End());
|
||||
} else if (!pseudo && depth > last_depth_) { // Start of message
|
||||
out.append(impl_->Start());
|
||||
}
|
||||
lastpos = nextpos;
|
||||
mLastDepth = depth;
|
||||
last_depth_ = depth;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static const char* pseudolocalizeChar(const char c) {
|
||||
static const char* PseudolocalizeChar(const char c) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
return "\u00e5";
|
||||
@@ -251,7 +251,7 @@ static const char* pseudolocalizeChar(const char c) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPossibleNormalPlaceholderEnd(const char c) {
|
||||
static bool IsPossibleNormalPlaceholderEnd(const char c) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
return true;
|
||||
@@ -300,12 +300,12 @@ static bool isPossibleNormalPlaceholderEnd(const char c) {
|
||||
}
|
||||
}
|
||||
|
||||
static std::string pseudoGenerateExpansion(const unsigned int length) {
|
||||
std::string result = k_expansion_string;
|
||||
static std::string PseudoGenerateExpansion(const unsigned int length) {
|
||||
std::string result = kExpansionString;
|
||||
const char* s = result.data();
|
||||
if (result.size() < length) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(length - result.size());
|
||||
result += PseudoGenerateExpansion(length - result.size());
|
||||
} else {
|
||||
int ext = 0;
|
||||
// Should contain only whole words, so looking for a space
|
||||
@@ -320,25 +320,25 @@ static std::string pseudoGenerateExpansion(const unsigned int length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::start() {
|
||||
std::string PseudoMethodAccent::Start() {
|
||||
std::string result;
|
||||
if (mDepth == 0) {
|
||||
if (depth_ == 0) {
|
||||
result = "[";
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth++;
|
||||
word_count_ = length_ = 0;
|
||||
depth_++;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::end() {
|
||||
std::string PseudoMethodAccent::End() {
|
||||
std::string result;
|
||||
if (mLength) {
|
||||
if (length_) {
|
||||
result += " ";
|
||||
result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
|
||||
result += PseudoGenerateExpansion(word_count_ > 3 ? length_ : length_ / 2);
|
||||
}
|
||||
mWordCount = mLength = 0;
|
||||
mDepth--;
|
||||
if (mDepth == 0) {
|
||||
word_count_ = length_ = 0;
|
||||
depth_--;
|
||||
if (depth_ == 0) {
|
||||
result += "]";
|
||||
}
|
||||
return result;
|
||||
@@ -349,7 +349,7 @@ std::string PseudoMethodAccent::end() {
|
||||
*
|
||||
* Note: This leaves placeholder syntax untouched.
|
||||
*/
|
||||
std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
std::string PseudoMethodAccent::Text(const StringPiece& source) {
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
const size_t I = source.size();
|
||||
@@ -365,7 +365,7 @@ std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
++i;
|
||||
c = s[i];
|
||||
chunk.append(&c, 1);
|
||||
if (isPossibleNormalPlaceholderEnd(c)) {
|
||||
if (IsPossibleNormalPlaceholderEnd(c)) {
|
||||
end = true;
|
||||
} else if (i + 1 < I && c == 't') {
|
||||
++i;
|
||||
@@ -375,24 +375,24 @@ std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
}
|
||||
}
|
||||
// Treat chunk as a placeholder unless it ends with %.
|
||||
result += ((c == '%') ? chunk : placeholder(chunk));
|
||||
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);
|
||||
std::string escape_text;
|
||||
escape_text.append(&c, 1);
|
||||
bool end = false;
|
||||
size_t htmlCodePos = i;
|
||||
while (!end && htmlCodePos < I) {
|
||||
++htmlCodePos;
|
||||
c = s[htmlCodePos];
|
||||
escapeText.append(&c, 1);
|
||||
size_t html_code_pos = i;
|
||||
while (!end && html_code_pos < I) {
|
||||
++html_code_pos;
|
||||
c = s[html_code_pos];
|
||||
escape_text.append(&c, 1);
|
||||
// Valid html code
|
||||
if (c == ';') {
|
||||
end = true;
|
||||
i = htmlCodePos;
|
||||
i = html_code_pos;
|
||||
}
|
||||
// Wrong html code
|
||||
else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
|
||||
@@ -400,8 +400,8 @@ std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
result += escapeText;
|
||||
if (escapeText != "<") {
|
||||
result += escape_text;
|
||||
if (escape_text != "<") {
|
||||
tag_closed = true;
|
||||
}
|
||||
continue;
|
||||
@@ -417,30 +417,30 @@ std::string PseudoMethodAccent::text(const StringPiece& source) {
|
||||
}
|
||||
} else {
|
||||
// This is a pure text that should be pseudolocalized
|
||||
const char* p = pseudolocalizeChar(c);
|
||||
const char* p = PseudolocalizeChar(c);
|
||||
if (p != nullptr) {
|
||||
result += p;
|
||||
} else {
|
||||
bool space = isspace(c);
|
||||
if (lastspace && !space) {
|
||||
mWordCount++;
|
||||
word_count_++;
|
||||
}
|
||||
lastspace = space;
|
||||
result.append(&c, 1);
|
||||
}
|
||||
// Count only pseudolocalizable chars and delimiters
|
||||
mLength++;
|
||||
length_++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
|
||||
std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
|
||||
// Surround a placeholder with brackets
|
||||
return k_placeholder_open + source.toString() + k_placeholder_close;
|
||||
return kPlaceholderOpen + source.ToString() + kPlaceholderClose;
|
||||
}
|
||||
|
||||
std::string PseudoMethodBidi::text(const StringPiece& source) {
|
||||
std::string PseudoMethodBidi::Text(const StringPiece& source) {
|
||||
const char* s = source.data();
|
||||
std::string result;
|
||||
bool lastspace = true;
|
||||
@@ -450,24 +450,24 @@ std::string PseudoMethodBidi::text(const StringPiece& source) {
|
||||
space = isspace(c);
|
||||
if (lastspace && !space) {
|
||||
// Word start
|
||||
result += k_rlm + k_rlo;
|
||||
result += kRlm + kRlo;
|
||||
} else if (!lastspace && space) {
|
||||
// Word end
|
||||
result += k_pdf + k_rlm;
|
||||
result += kPdf + kRlm;
|
||||
}
|
||||
lastspace = space;
|
||||
result.append(&c, 1);
|
||||
}
|
||||
if (!lastspace) {
|
||||
// End of last word
|
||||
result += k_pdf + k_rlm;
|
||||
result += kPdf + kRlm;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PseudoMethodBidi::placeholder(const StringPiece& source) {
|
||||
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;
|
||||
return kRlm + kRlo + source.ToString() + kPdf + kRlm;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,22 +17,23 @@
|
||||
#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H
|
||||
#define AAPT_COMPILE_PSEUDOLOCALIZE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPool.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <memory>
|
||||
|
||||
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;
|
||||
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 {
|
||||
@@ -44,14 +45,14 @@ class Pseudolocalizer {
|
||||
};
|
||||
|
||||
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);
|
||||
void SetMethod(Method method);
|
||||
std::string Start() { return impl_->Start(); }
|
||||
std::string End() { return impl_->End(); }
|
||||
std::string Text(const StringPiece& text);
|
||||
|
||||
private:
|
||||
std::unique_ptr<PseudoMethodImpl> mImpl;
|
||||
size_t mLastDepth;
|
||||
std::unique_ptr<PseudoMethodImpl> impl_;
|
||||
size_t last_depth_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,33 +15,32 @@
|
||||
*/
|
||||
|
||||
#include "compile/Pseudolocalizer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "test/Test.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
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,
|
||||
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();
|
||||
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(
|
||||
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();
|
||||
std::string result = pseudo.Start() + pseudo.Text(in1) + pseudo.Text(in2) +
|
||||
pseudo.Text(in3) + pseudo.End();
|
||||
if (result != expected) {
|
||||
return ::testing::AssertionFailure() << expected << " != " << result;
|
||||
}
|
||||
@@ -49,49 +48,49 @@ static ::testing::AssertionResult compoundHelper(
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, NoPseudolocalization) {
|
||||
EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world",
|
||||
EXPECT_TRUE(SimpleHelper("", "", Pseudolocalizer::Method::kNone));
|
||||
EXPECT_TRUE(SimpleHelper("Hello, world", "Hello, world",
|
||||
Pseudolocalizer::Method::kNone));
|
||||
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "", "Hello, world",
|
||||
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]",
|
||||
EXPECT_TRUE(SimpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(SimpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]",
|
||||
EXPECT_TRUE(SimpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]",
|
||||
EXPECT_TRUE(SimpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
|
||||
SimpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(
|
||||
compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]",
|
||||
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(
|
||||
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(
|
||||
EXPECT_TRUE(SimpleHelper(
|
||||
" word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
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",
|
||||
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(
|
||||
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",
|
||||
@@ -100,33 +99,33 @@ TEST(PseudolocalizerTest, PlaintextBidi) {
|
||||
|
||||
TEST(PseudolocalizerTest, SimpleICU) {
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
|
||||
EXPECT_TRUE(SimpleHelper("{placeholder}", "[»{placeholder}«]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]",
|
||||
EXPECT_TRUE(SimpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
|
||||
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}",
|
||||
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",
|
||||
EXPECT_TRUE(CompoundHelper("{USER}", " ", "is offline",
|
||||
"[»{USER}« îš öƒƒļîñé one two]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
|
||||
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(
|
||||
EXPECT_TRUE(SimpleHelper(
|
||||
"{placeholder}",
|
||||
"\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
|
||||
Pseudolocalizer::Method::kBidi));
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
EXPECT_TRUE(SimpleHelper(
|
||||
"{COUNT, plural, one {one} other {other}}",
|
||||
"{COUNT, plural, "
|
||||
"one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} "
|
||||
@@ -136,30 +135,30 @@ TEST(PseudolocalizerTest, ICUBidi) {
|
||||
|
||||
TEST(PseudolocalizerTest, Escaping) {
|
||||
// Single-fragment messages
|
||||
EXPECT_TRUE(simpleHelper("'{USER'} is offline",
|
||||
EXPECT_TRUE(SimpleHelper("'{USER'} is offline",
|
||||
"['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
// Multi-fragment messages
|
||||
EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
|
||||
EXPECT_TRUE(CompoundHelper("'{USER}", " ", "''is offline",
|
||||
"['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
}
|
||||
|
||||
TEST(PseudolocalizerTest, PluralsAndSelects) {
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
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}}",
|
||||
SimpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}",
|
||||
"[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} "
|
||||
"other {# ḿîļéš one two}}]",
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(simpleHelper(
|
||||
EXPECT_TRUE(SimpleHelper(
|
||||
"{1, select, female {{1} added you} "
|
||||
"male {{1} added you} other {{1} added you}}",
|
||||
"[{1, select, female {»{1}« åððéð ýöû one two} "
|
||||
@@ -167,7 +166,7 @@ TEST(PseudolocalizerTest, PluralsAndSelects) {
|
||||
Pseudolocalizer::Method::kAccent));
|
||||
|
||||
EXPECT_TRUE(
|
||||
compoundHelper("{COUNT, plural, one {Delete a file} "
|
||||
CompoundHelper("{COUNT, plural, one {Delete a file} "
|
||||
"other {Delete ",
|
||||
"{COUNT}", " files}}",
|
||||
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
|
||||
@@ -177,7 +176,7 @@ TEST(PseudolocalizerTest, PluralsAndSelects) {
|
||||
|
||||
TEST(PseudolocalizerTest, NestedICU) {
|
||||
EXPECT_TRUE(
|
||||
simpleHelper("{person, select, "
|
||||
SimpleHelper("{person, select, "
|
||||
"female {"
|
||||
"{num_circles, plural,"
|
||||
"=0{{person} didn't add you to any of her circles.}"
|
||||
@@ -222,9 +221,9 @@ TEST(PseudolocalizerTest, NestedICU) {
|
||||
|
||||
TEST(PseudolocalizerTest, RedefineMethod) {
|
||||
Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
|
||||
std::string result = pseudo.text("Hello, ");
|
||||
pseudo.setMethod(Pseudolocalizer::Method::kNone);
|
||||
result += pseudo.text("world!");
|
||||
std::string result = pseudo.Text("Hello, ");
|
||||
pseudo.SetMethod(Pseudolocalizer::Method::kNone);
|
||||
result += pseudo.Text("world!");
|
||||
ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,55 +15,59 @@
|
||||
*/
|
||||
|
||||
#include "compile/XmlIdCollector.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
|
||||
static bool cmp_name(const SourcedResourceName& a, const ResourceNameRef& b) {
|
||||
return a.name < b;
|
||||
}
|
||||
|
||||
struct IdCollector : public xml::Visitor {
|
||||
using xml::Visitor::visit;
|
||||
public:
|
||||
using xml::Visitor::Visit;
|
||||
|
||||
std::vector<SourcedResourceName>* mOutSymbols;
|
||||
explicit IdCollector(std::vector<SourcedResourceName>* out_symbols)
|
||||
: out_symbols_(out_symbols) {}
|
||||
|
||||
explicit IdCollector(std::vector<SourcedResourceName>* outSymbols)
|
||||
: mOutSymbols(outSymbols) {}
|
||||
|
||||
void visit(xml::Element* element) override {
|
||||
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 (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});
|
||||
auto iter = std::lower_bound(out_symbols_->begin(),
|
||||
out_symbols_->end(), name, cmp_name);
|
||||
if (iter == out_symbols_->end() || iter->name != name) {
|
||||
out_symbols_->insert(iter,
|
||||
SourcedResourceName{name.ToResourceName(),
|
||||
element->line_number});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xml::Visitor::visit(element);
|
||||
xml::Visitor::Visit(element);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SourcedResourceName>* out_symbols_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
|
||||
xmlRes->file.exportedSymbols.clear();
|
||||
IdCollector collector(&xmlRes->file.exportedSymbols);
|
||||
xmlRes->root->accept(&collector);
|
||||
bool XmlIdCollector::Consume(IAaptContext* context, xml::XmlResource* xmlRes) {
|
||||
xmlRes->file.exported_symbols.clear();
|
||||
IdCollector collector(&xmlRes->file.exported_symbols);
|
||||
xmlRes->root->Accept(&collector);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
namespace aapt {
|
||||
|
||||
struct XmlIdCollector : public IXmlResourceConsumer {
|
||||
bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
|
||||
bool Consume(IAaptContext* context, xml::XmlResource* xml_res) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,18 +15,17 @@
|
||||
*/
|
||||
|
||||
#include "compile/XmlIdCollector.h"
|
||||
#include "test/Builders.h"
|
||||
#include "test/Context.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
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">
|
||||
@@ -35,34 +34,34 @@ TEST(XmlIdCollectorTest, CollectsIds) {
|
||||
</View>)EOF");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
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}));
|
||||
1, std::count(doc->file.exported_symbols.begin(),
|
||||
doc->file.exported_symbols.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}));
|
||||
1, std::count(doc->file.exported_symbols.begin(),
|
||||
doc->file.exported_symbols.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}));
|
||||
1, std::count(doc->file.exported_symbols.begin(),
|
||||
doc->file.exported_symbols.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\"/>");
|
||||
test::BuildXmlDom("<View foo=\"@+string/foo\"/>");
|
||||
|
||||
XmlIdCollector collector;
|
||||
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
|
||||
ASSERT_TRUE(collector.Consume(context.get(), doc.get()));
|
||||
|
||||
EXPECT_TRUE(doc->file.exportedSymbols.empty());
|
||||
EXPECT_TRUE(doc->file.exported_symbols.empty());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Flags.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ValueVisitor.h"
|
||||
@@ -22,74 +24,72 @@
|
||||
#include "process/SymbolTable.h"
|
||||
#include "unflatten/BinaryResourceParser.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class DiffContext : public IAaptContext {
|
||||
public:
|
||||
const std::string& getCompilationPackage() override { return mEmpty; }
|
||||
const std::string& GetCompilationPackage() override { return empty_; }
|
||||
|
||||
uint8_t getPackageId() override { return 0x0; }
|
||||
uint8_t GetPackageId() override { return 0x0; }
|
||||
|
||||
IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
|
||||
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
|
||||
|
||||
NameMangler* getNameMangler() override { return &mNameMangler; }
|
||||
NameMangler* GetNameMangler() override { return &name_mangler_; }
|
||||
|
||||
SymbolTable* getExternalSymbols() override { return &mSymbolTable; }
|
||||
SymbolTable* GetExternalSymbols() override { return &symbol_table_; }
|
||||
|
||||
bool verbose() override { return false; }
|
||||
bool IsVerbose() 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;
|
||||
std::string empty_;
|
||||
StdErrDiagnostics diagnostics_;
|
||||
NameMangler name_mangler_ = NameMangler(NameManglerPolicy{});
|
||||
SymbolTable symbol_table_;
|
||||
};
|
||||
|
||||
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)) {}
|
||||
: source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
|
||||
|
||||
io::IFileCollection* getFileCollection() { return mApk.get(); }
|
||||
io::IFileCollection* GetFileCollection() { return apk_.get(); }
|
||||
|
||||
ResourceTable* getResourceTable() { return mTable.get(); }
|
||||
ResourceTable* GetResourceTable() { return table_.get(); }
|
||||
|
||||
const Source& getSource() { return mSource; }
|
||||
const Source& GetSource() { return source_; }
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
std::unique_ptr<io::IFileCollection> mApk;
|
||||
std::unique_ptr<ResourceTable> mTable;
|
||||
Source source_;
|
||||
std::unique_ptr<io::IFileCollection> apk_;
|
||||
std::unique_ptr<ResourceTable> table_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
|
||||
};
|
||||
|
||||
static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context,
|
||||
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);
|
||||
io::ZipFileCollection::Create(path, &error);
|
||||
if (!apk) {
|
||||
context->getDiagnostics()->error(DiagMessage(source) << error);
|
||||
context->GetDiagnostics()->Error(DiagMessage(source) << error);
|
||||
return {};
|
||||
}
|
||||
|
||||
io::IFile* file = apk->findFile("resources.arsc");
|
||||
io::IFile* file = apk->FindFile("resources.arsc");
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(source)
|
||||
context->GetDiagnostics()->Error(DiagMessage(source)
|
||||
<< "no resources.arsc found");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
std::unique_ptr<io::IData> data = file->OpenAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(source)
|
||||
context->GetDiagnostics()->Error(DiagMessage(source)
|
||||
<< "could not open resources.arsc");
|
||||
return {};
|
||||
}
|
||||
@@ -97,276 +97,281 @@ static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context,
|
||||
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), source, data->data(),
|
||||
data->size());
|
||||
if (!parser.parse()) {
|
||||
if (!parser.Parse()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
|
||||
}
|
||||
|
||||
static void emitDiffLine(const Source& source, const StringPiece& message) {
|
||||
static void EmitDiffLine(const Source& source, const StringPiece& message) {
|
||||
std::cerr << source << ": " << message << "\n";
|
||||
}
|
||||
|
||||
static bool isSymbolVisibilityDifferent(const Symbol& symbolA,
|
||||
const Symbol& symbolB) {
|
||||
return symbolA.state != symbolB.state;
|
||||
static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a,
|
||||
const Symbol& symbol_b) {
|
||||
return symbol_a.state != symbol_b.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;
|
||||
static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a,
|
||||
const Symbol& symbol_b, const Maybe<Id>& id_b) {
|
||||
if (symbol_a.state == SymbolState::kPublic ||
|
||||
symbol_b.state == SymbolState::kPublic) {
|
||||
return id_a != id_b;
|
||||
}
|
||||
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());
|
||||
static bool EmitResourceConfigValueDiff(
|
||||
IAaptContext* context, LoadedApk* apk_a, ResourceTablePackage* pkg_a,
|
||||
ResourceTableType* type_a, ResourceEntry* entry_a,
|
||||
ResourceConfigValue* config_value_a, LoadedApk* apk_b,
|
||||
ResourceTablePackage* pkg_b, ResourceTableType* type_b,
|
||||
ResourceEntry* entry_b, ResourceConfigValue* config_value_b) {
|
||||
Value* value_a = config_value_a->value.get();
|
||||
Value* value_b = config_value_b->value.get();
|
||||
if (!value_a->Equals(value_b)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "value " << pkg_a->name << ":" << type_a->type << "/"
|
||||
<< entry_a->name << " config=" << config_value_a->config
|
||||
<< " does not match:\n";
|
||||
value_a->Print(&str_stream);
|
||||
str_stream << "\n vs \n";
|
||||
value_b->Print(&str_stream);
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool emitResourceEntryDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA,
|
||||
ResourceEntry* entryA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB,
|
||||
ResourceTableType* typeB,
|
||||
ResourceEntry* entryB) {
|
||||
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
ResourceTablePackage* pkg_a,
|
||||
ResourceTableType* type_a,
|
||||
ResourceEntry* entry_a, LoadedApk* apk_b,
|
||||
ResourceTablePackage* pkg_b,
|
||||
ResourceTableType* type_b,
|
||||
ResourceEntry* entry_b) {
|
||||
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());
|
||||
for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
|
||||
ResourceConfigValue* config_value_b =
|
||||
entry_b->FindValue(config_value_a->config);
|
||||
if (!config_value_b) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
|
||||
<< entry_a->name << " config=" << config_value_a->config;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
|
||||
configValueA.get(), apkB, pkgB, typeB,
|
||||
entryB, configValueB);
|
||||
diff |= EmitResourceConfigValueDiff(
|
||||
context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), apk_b,
|
||||
pkg_b, type_b, entry_b, config_value_b);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
|
||||
ResourceConfigValue* config_value_a =
|
||||
entry_a->FindValue(config_value_b->config);
|
||||
if (!config_value_a) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/"
|
||||
<< entry_b->name << " config=" << config_value_b->config;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool emitResourceTypeDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA,
|
||||
ResourceTableType* typeA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB,
|
||||
ResourceTableType* typeB) {
|
||||
static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
ResourceTablePackage* pkg_a,
|
||||
ResourceTableType* type_a, LoadedApk* apk_b,
|
||||
ResourceTablePackage* pkg_b,
|
||||
ResourceTableType* type_b) {
|
||||
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());
|
||||
for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
|
||||
ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
|
||||
if (!entry_b) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
|
||||
<< entry_a->name;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.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";
|
||||
if (IsSymbolVisibilityDifferent(entry_a->symbol_status,
|
||||
entry_b->symbol_status)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
|
||||
<< " has different visibility (";
|
||||
if (entry_b->symbol_status.state == SymbolState::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
str_stream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
str_stream << " vs ";
|
||||
if (entry_a->symbol_status.state == SymbolState::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
str_stream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.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 if (IsIdDiff(entry_a->symbol_status, entry_a->id,
|
||||
entry_b->symbol_status, entry_b->id)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
|
||||
<< " has different public ID (";
|
||||
if (entry_b->id) {
|
||||
str_stream << "0x" << std::hex << entry_b->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (entryA->id) {
|
||||
strStream << "0x " << std::hex << entryA->id.value();
|
||||
str_stream << " vs ";
|
||||
if (entry_a->id) {
|
||||
str_stream << "0x " << std::hex << entry_a->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
|
||||
apkB, pkgB, typeB, entryB);
|
||||
diff |=
|
||||
EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(),
|
||||
apk_b, pkg_b, type_b, entry_b);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) {
|
||||
ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
|
||||
if (!entry_a) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/"
|
||||
<< entry_b->name;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
ResourceTablePackage* pkgA, LoadedApk* apkB,
|
||||
ResourceTablePackage* pkgB) {
|
||||
static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
ResourceTablePackage* pkg_a,
|
||||
LoadedApk* apk_b,
|
||||
ResourceTablePackage* pkg_b) {
|
||||
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());
|
||||
for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
|
||||
ResourceTableType* type_b = pkg_b->FindType(type_a->type);
|
||||
if (!type_b) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing " << pkg_a->name << ":" << type_a->type;
|
||||
EmitDiffLine(apk_a->GetSource(), str_stream.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";
|
||||
if (IsSymbolVisibilityDifferent(type_a->symbol_status,
|
||||
type_b->symbol_status)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a->name << ":" << type_a->type
|
||||
<< " has different visibility (";
|
||||
if (type_b->symbol_status.state == SymbolState::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
str_stream << "PRIVATE";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->symbolStatus.state == SymbolState::kPublic) {
|
||||
strStream << "PUBLIC";
|
||||
str_stream << " vs ";
|
||||
if (type_a->symbol_status.state == SymbolState::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
strStream << "PRIVATE";
|
||||
str_stream << "PRIVATE";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.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 if (IsIdDiff(type_a->symbol_status, type_a->id,
|
||||
type_b->symbol_status, type_b->id)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a->name << ":" << type_a->type
|
||||
<< " has different public ID (";
|
||||
if (type_b->id) {
|
||||
str_stream << "0x" << std::hex << type_b->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (typeA->id) {
|
||||
strStream << "0x " << std::hex << typeA->id.value();
|
||||
str_stream << " vs ";
|
||||
if (type_a->id) {
|
||||
str_stream << "0x " << std::hex << type_a->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB,
|
||||
typeB);
|
||||
diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b,
|
||||
pkg_b, type_b);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
|
||||
ResourceTableType* type_a = pkg_a->FindType(type_b->type);
|
||||
if (!type_a) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new type " << pkg_b->name << ":" << type_b->type;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
}
|
||||
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* apk_a,
|
||||
LoadedApk* apk_b) {
|
||||
ResourceTable* table_a = apk_a->GetResourceTable();
|
||||
ResourceTable* table_b = apk_b->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());
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
|
||||
ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
|
||||
if (!pkg_b) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing package " << pkg_a->name;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.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();
|
||||
if (pkg_a->id != pkg_b->id) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "package '" << pkg_a->name << "' has different id (";
|
||||
if (pkg_b->id) {
|
||||
str_stream << "0x" << std::hex << pkg_b->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << " vs ";
|
||||
if (pkgA->id) {
|
||||
strStream << "0x" << std::hex << pkgA->id.value();
|
||||
str_stream << " vs ";
|
||||
if (pkg_a->id) {
|
||||
str_stream << "0x" << std::hex << pkg_a->id.value();
|
||||
} else {
|
||||
strStream << "none";
|
||||
str_stream << "none";
|
||||
}
|
||||
strStream << ")";
|
||||
emitDiffLine(apkB->getSource(), strStream.str());
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
|
||||
diff |=
|
||||
EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
|
||||
ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
|
||||
if (!pkg_a) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new package " << pkg_b->name;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
}
|
||||
}
|
||||
@@ -375,49 +380,49 @@ static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA,
|
||||
|
||||
class ZeroingReferenceVisitor : public ValueVisitor {
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
using ValueVisitor::Visit;
|
||||
|
||||
void visit(Reference* ref) override {
|
||||
void Visit(Reference* ref) override {
|
||||
if (ref->name && ref->id) {
|
||||
if (ref->id.value().packageId() == 0x7f) {
|
||||
if (ref->id.value().package_id() == 0x7f) {
|
||||
ref->id = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void zeroOutAppReferences(ResourceTable* table) {
|
||||
static void ZeroOutAppReferences(ResourceTable* table) {
|
||||
ZeroingReferenceVisitor visitor;
|
||||
visitAllValuesInTable(table, &visitor);
|
||||
VisitAllValuesInTable(table, &visitor);
|
||||
}
|
||||
|
||||
int diff(const std::vector<StringPiece>& args) {
|
||||
int Diff(const std::vector<StringPiece>& args) {
|
||||
DiffContext context;
|
||||
|
||||
Flags flags;
|
||||
if (!flags.parse("aapt2 diff", args, &std::cerr)) {
|
||||
if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (flags.getArgs().size() != 2u) {
|
||||
if (flags.GetArgs().size() != 2u) {
|
||||
std::cerr << "must have two apks as arguments.\n\n";
|
||||
flags.usage("aapt2 diff", &std::cerr);
|
||||
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) {
|
||||
std::unique_ptr<LoadedApk> apk_a =
|
||||
LoadApkFromPath(&context, flags.GetArgs()[0]);
|
||||
std::unique_ptr<LoadedApk> apk_b =
|
||||
LoadApkFromPath(&context, flags.GetArgs()[1]);
|
||||
if (!apk_a || !apk_b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Zero out Application IDs in references.
|
||||
zeroOutAppReferences(apkA->getResourceTable());
|
||||
zeroOutAppReferences(apkB->getResourceTable());
|
||||
ZeroOutAppReferences(apk_a->GetResourceTable());
|
||||
ZeroOutAppReferences(apk_b->GetResourceTable());
|
||||
|
||||
if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
|
||||
if (EmitResourceTableDiff(&context, apk_a.get(), apk_b.get())) {
|
||||
// We emitted a diff, so return 1 (failure).
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Debug.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "Flags.h"
|
||||
@@ -24,20 +26,14 @@
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// struct DumpOptions {
|
||||
//
|
||||
//};
|
||||
|
||||
void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data,
|
||||
void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data,
|
||||
size_t len, const Source& source, IAaptContext* context) {
|
||||
std::unique_ptr<ResourceFile> file =
|
||||
deserializeCompiledFileFromPb(pbFile, source, context->getDiagnostics());
|
||||
DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics());
|
||||
if (!file) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
context->GetDiagnostics()->Warn(DiagMessage()
|
||||
<< "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
@@ -47,50 +43,50 @@ void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data,
|
||||
<< "Source: " << file->source << "\n";
|
||||
}
|
||||
|
||||
void tryDumpFile(IAaptContext* context, const std::string& filePath) {
|
||||
void TryDumpFile(IAaptContext* context, const std::string& file_path) {
|
||||
std::unique_ptr<ResourceTable> table;
|
||||
|
||||
std::string err;
|
||||
std::unique_ptr<io::ZipFileCollection> zip =
|
||||
io::ZipFileCollection::create(filePath, &err);
|
||||
io::ZipFileCollection::Create(file_path, &err);
|
||||
if (zip) {
|
||||
io::IFile* file = zip->findFile("resources.arsc.flat");
|
||||
io::IFile* file = zip->FindFile("resources.arsc.flat");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
std::unique_ptr<io::IData> data = file->OpenAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(
|
||||
DiagMessage(filePath) << "failed to open resources.arsc.flat");
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(file_path) << "failed to open resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
|
||||
pb::ResourceTable pbTable;
|
||||
if (!pbTable.ParseFromArray(data->data(), data->size())) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
pb::ResourceTable pb_table;
|
||||
if (!pb_table.ParseFromArray(data->data(), data->size())) {
|
||||
context->GetDiagnostics()->Error(DiagMessage(file_path)
|
||||
<< "invalid resources.arsc.flat");
|
||||
return;
|
||||
}
|
||||
|
||||
table = deserializeTableFromPb(pbTable, Source(filePath),
|
||||
context->getDiagnostics());
|
||||
table = DeserializeTableFromPb(pb_table, Source(file_path),
|
||||
context->GetDiagnostics());
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
file = zip->findFile("resources.arsc");
|
||||
file = zip->FindFile("resources.arsc");
|
||||
if (file) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
std::unique_ptr<io::IData> data = file->OpenAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath)
|
||||
context->GetDiagnostics()->Error(DiagMessage(file_path)
|
||||
<< "failed to open resources.arsc");
|
||||
return;
|
||||
}
|
||||
|
||||
table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(context, table.get(), Source(filePath),
|
||||
BinaryResourceParser parser(context, table.get(), Source(file_path),
|
||||
data->data(), data->size());
|
||||
if (!parser.parse()) {
|
||||
if (!parser.Parse()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -98,109 +94,109 @@ void tryDumpFile(IAaptContext* context, const std::string& filePath) {
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
|
||||
Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
|
||||
if (!file) {
|
||||
context->getDiagnostics()->error(DiagMessage(filePath) << err);
|
||||
context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
|
||||
return;
|
||||
}
|
||||
|
||||
android::FileMap* fileMap = &file.value();
|
||||
android::FileMap* file_map = &file.value();
|
||||
|
||||
// Try as a compiled table.
|
||||
pb::ResourceTable pbTable;
|
||||
if (pbTable.ParseFromArray(fileMap->getDataPtr(),
|
||||
fileMap->getDataLength())) {
|
||||
table = deserializeTableFromPb(pbTable, Source(filePath),
|
||||
context->getDiagnostics());
|
||||
pb::ResourceTable pb_table;
|
||||
if (pb_table.ParseFromArray(file_map->getDataPtr(),
|
||||
file_map->getDataLength())) {
|
||||
table = DeserializeTableFromPb(pb_table, Source(file_path),
|
||||
context->GetDiagnostics());
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
// Try as a compiled file.
|
||||
CompiledFileInputStream input(fileMap->getDataPtr(),
|
||||
fileMap->getDataLength());
|
||||
CompiledFileInputStream input(file_map->getDataPtr(),
|
||||
file_map->getDataLength());
|
||||
|
||||
uint32_t numFiles = 0;
|
||||
if (!input.ReadLittleEndian32(&numFiles)) {
|
||||
uint32_t num_files = 0;
|
||||
if (!input.ReadLittleEndian32(&num_files)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < numFiles; i++) {
|
||||
pb::CompiledFile compiledFile;
|
||||
if (!input.ReadCompiledFile(&compiledFile)) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
for (uint32_t i = 0; i < num_files; i++) {
|
||||
pb::CompiledFile compiled_file;
|
||||
if (!input.ReadCompiledFile(&compiled_file)) {
|
||||
context->GetDiagnostics()->Warn(DiagMessage()
|
||||
<< "failed to read compiled file");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t offset, len;
|
||||
if (!input.ReadDataMetaData(&offset, &len)) {
|
||||
context->getDiagnostics()->warn(DiagMessage()
|
||||
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);
|
||||
static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
|
||||
DumpCompiledFile(compiled_file, data, len, Source(file_path), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (table) {
|
||||
DebugPrintTableOptions debugPrintTableOptions;
|
||||
debugPrintTableOptions.showSources = true;
|
||||
Debug::printTable(table.get(), debugPrintTableOptions);
|
||||
DebugPrintTableOptions options;
|
||||
options.show_sources = true;
|
||||
Debug::PrintTable(table.get(), options);
|
||||
}
|
||||
}
|
||||
|
||||
class DumpContext : public IAaptContext {
|
||||
public:
|
||||
IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
|
||||
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
|
||||
|
||||
NameMangler* getNameMangler() override {
|
||||
NameMangler* GetNameMangler() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& getCompilationPackage() override {
|
||||
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 {
|
||||
SymbolTable* GetExternalSymbols() override {
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool verbose() override { return mVerbose; }
|
||||
bool IsVerbose() override { return verbose_; }
|
||||
|
||||
void setVerbose(bool val) { mVerbose = val; }
|
||||
void SetVerbose(bool val) { verbose_ = val; }
|
||||
|
||||
int getMinSdkVersion() override { return 0; }
|
||||
int GetMinSdkVersion() override { return 0; }
|
||||
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
bool mVerbose = false;
|
||||
StdErrDiagnostics diagnostics_;
|
||||
bool verbose_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for dump command.
|
||||
*/
|
||||
int dump(const std::vector<StringPiece>& args) {
|
||||
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)) {
|
||||
Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
|
||||
if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DumpContext context;
|
||||
context.setVerbose(verbose);
|
||||
context.SetVerbose(verbose);
|
||||
|
||||
for (const std::string& arg : flags.getArgs()) {
|
||||
tryDumpFile(&context, arg);
|
||||
for (const std::string& arg : flags.GetArgs()) {
|
||||
TryDumpFile(&context, arg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -15,44 +15,45 @@
|
||||
*/
|
||||
|
||||
#include "filter/ConfigFilter.h"
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
void AxisConfigFilter::addConfig(ConfigDescription config) {
|
||||
uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
|
||||
void AxisConfigFilter::AddConfig(ConfigDescription config) {
|
||||
uint32_t diff_mask = ConfigDescription::DefaultConfig().diff(config);
|
||||
|
||||
// Ignore the version
|
||||
diffMask &= ~android::ResTable_config::CONFIG_VERSION;
|
||||
diff_mask &= ~android::ResTable_config::CONFIG_VERSION;
|
||||
|
||||
// Ignore any densities. Those are best handled in --preferred-density
|
||||
if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
|
||||
if ((diff_mask & android::ResTable_config::CONFIG_DENSITY) != 0) {
|
||||
config.density = 0;
|
||||
diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
|
||||
diff_mask &= ~android::ResTable_config::CONFIG_DENSITY;
|
||||
}
|
||||
|
||||
mConfigs.insert(std::make_pair(config, diffMask));
|
||||
mConfigMask |= diffMask;
|
||||
configs_.insert(std::make_pair(config, diff_mask));
|
||||
config_mask_ |= diff_mask;
|
||||
}
|
||||
|
||||
bool AxisConfigFilter::match(const ConfigDescription& config) const {
|
||||
const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
|
||||
if ((mConfigMask & mask) == 0) {
|
||||
bool AxisConfigFilter::Match(const ConfigDescription& config) const {
|
||||
const uint32_t mask = ConfigDescription::DefaultConfig().diff(config);
|
||||
if ((config_mask_ & mask) == 0) {
|
||||
// The two configurations don't have any common axis.
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t matchedAxis = 0;
|
||||
for (const auto& entry : mConfigs) {
|
||||
uint32_t matched_axis = 0;
|
||||
for (const auto& entry : configs_) {
|
||||
const ConfigDescription& target = entry.first;
|
||||
const uint32_t diffMask = entry.second;
|
||||
const uint32_t diff_mask = entry.second;
|
||||
uint32_t diff = target.diff(config);
|
||||
if ((diff & diffMask) == 0) {
|
||||
if ((diff & diff_mask) == 0) {
|
||||
// Mark the axis that was matched.
|
||||
matchedAxis |= diffMask;
|
||||
} else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
|
||||
matched_axis |= diff_mask;
|
||||
} else if ((diff & diff_mask) == 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.
|
||||
@@ -60,10 +61,10 @@ bool AxisConfigFilter::match(const ConfigDescription& config) const {
|
||||
memcmp(config.language, target.language, sizeof(config.language)) ==
|
||||
0) {
|
||||
if (config.country[0] == 0) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
|
||||
matched_axis |= android::ResTable_config::CONFIG_LOCALE;
|
||||
}
|
||||
}
|
||||
} else if ((diff & diffMask) ==
|
||||
} else if ((diff & diff_mask) ==
|
||||
android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
|
||||
// Special case if the smallest screen width doesn't match. We check that
|
||||
// the
|
||||
@@ -71,11 +72,11 @@ bool AxisConfigFilter::match(const ConfigDescription& config) const {
|
||||
// specified.
|
||||
if (config.smallestScreenWidthDp != 0 &&
|
||||
config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
|
||||
matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
matched_axis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchedAxis == (mConfigMask & mask);
|
||||
return matched_axis == (config_mask_ & mask);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
#ifndef AAPT_FILTER_CONFIGFILTER_H
|
||||
#define AAPT_FILTER_CONFIGFILTER_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ class IConfigFilter {
|
||||
/**
|
||||
* Returns true if the filter matches the configuration, false otherwise.
|
||||
*/
|
||||
virtual bool match(const ConfigDescription& config) const = 0;
|
||||
virtual bool Match(const ConfigDescription& config) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,13 +50,13 @@ class IConfigFilter {
|
||||
*/
|
||||
class AxisConfigFilter : public IConfigFilter {
|
||||
public:
|
||||
void addConfig(ConfigDescription config);
|
||||
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;
|
||||
std::set<std::pair<ConfigDescription, uint32_t>> configs_;
|
||||
uint32_t config_mask_ = 0;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,99 +15,98 @@
|
||||
*/
|
||||
|
||||
#include "filter/ConfigFilter.h"
|
||||
#include "test/Common.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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")));
|
||||
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"));
|
||||
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
|
||||
|
||||
@@ -15,131 +15,139 @@
|
||||
*/
|
||||
|
||||
#include "flatten/Archive.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ziparchive/zip_writer.h>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
#include "ziparchive/zip_writer.h"
|
||||
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
struct DirectoryWriter : public IArchiveWriter {
|
||||
std::string mOutDir;
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
|
||||
class DirectoryWriter : public IArchiveWriter {
|
||||
public:
|
||||
DirectoryWriter() = default;
|
||||
|
||||
bool open(IDiagnostics* diag, const StringPiece& outDir) {
|
||||
mOutDir = outDir.toString();
|
||||
file::FileType type = file::getFileType(mOutDir);
|
||||
bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
|
||||
dir_ = out_dir.ToString();
|
||||
file::FileType type = file::GetFileType(dir_);
|
||||
if (type == file::FileType::kNonexistant) {
|
||||
diag->error(DiagMessage() << "directory " << mOutDir
|
||||
<< " does not exist");
|
||||
diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
|
||||
return false;
|
||||
} else if (type != file::FileType::kDirectory) {
|
||||
diag->error(DiagMessage() << mOutDir << " is not a directory");
|
||||
diag->Error(DiagMessage() << dir_ << " is not a directory");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (mFile) {
|
||||
bool StartEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (file_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fullPath = mOutDir;
|
||||
file::appendPath(&fullPath, path);
|
||||
file::mkdirs(file::getStem(fullPath));
|
||||
std::string full_path = dir_;
|
||||
file::AppendPath(&full_path, path);
|
||||
file::mkdirs(file::GetStem(full_path));
|
||||
|
||||
mFile = {fopen(fullPath.data(), "wb"), fclose};
|
||||
if (!mFile) {
|
||||
file_ = {fopen(full_path.data(), "wb"), fclose};
|
||||
if (!file_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
if (!mFile) {
|
||||
bool WriteEntry(const BigBuffer& buffer) override {
|
||||
if (!file_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
|
||||
mFile.reset(nullptr);
|
||||
if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
|
||||
file_.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);
|
||||
bool WriteEntry(const void* data, size_t len) override {
|
||||
if (fwrite(data, 1, len, file_.get()) != len) {
|
||||
file_.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
if (!mFile) {
|
||||
bool FinishEntry() override {
|
||||
if (!file_) {
|
||||
return false;
|
||||
}
|
||||
mFile.reset(nullptr);
|
||||
file_.reset(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
|
||||
|
||||
std::string dir_;
|
||||
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
|
||||
};
|
||||
|
||||
struct ZipFileWriter : public IArchiveWriter {
|
||||
std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose};
|
||||
std::unique_ptr<ZipWriter> mWriter;
|
||||
class ZipFileWriter : public IArchiveWriter {
|
||||
public:
|
||||
ZipFileWriter() = default;
|
||||
|
||||
bool open(IDiagnostics* diag, const StringPiece& path) {
|
||||
mFile = {fopen(path.data(), "w+b"), fclose};
|
||||
if (!mFile) {
|
||||
diag->error(DiagMessage() << "failed to open " << path << ": "
|
||||
bool Open(IDiagnostics* diag, const StringPiece& path) {
|
||||
file_ = {fopen(path.data(), "w+b"), fclose};
|
||||
if (!file_) {
|
||||
diag->Error(DiagMessage() << "failed to Open " << path << ": "
|
||||
<< strerror(errno));
|
||||
return false;
|
||||
}
|
||||
mWriter = util::make_unique<ZipWriter>(mFile.get());
|
||||
writer_ = util::make_unique<ZipWriter>(file_.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (!mWriter) {
|
||||
bool StartEntry(const StringPiece& path, uint32_t flags) override {
|
||||
if (!writer_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t zipFlags = 0;
|
||||
size_t zip_flags = 0;
|
||||
if (flags & ArchiveEntry::kCompress) {
|
||||
zipFlags |= ZipWriter::kCompress;
|
||||
zip_flags |= ZipWriter::kCompress;
|
||||
}
|
||||
|
||||
if (flags & ArchiveEntry::kAlign) {
|
||||
zipFlags |= ZipWriter::kAlign32;
|
||||
zip_flags |= ZipWriter::kAlign32;
|
||||
}
|
||||
|
||||
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
|
||||
int32_t result = writer_->StartEntry(path.data(), zip_flags);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeEntry(const void* data, size_t len) override {
|
||||
int32_t result = mWriter->WriteBytes(data, len);
|
||||
bool WriteEntry(const void* data, size_t len) override {
|
||||
int32_t result = writer_->WriteBytes(data, len);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeEntry(const BigBuffer& buffer) override {
|
||||
bool WriteEntry(const BigBuffer& buffer) override {
|
||||
for (const BigBuffer::Block& b : buffer) {
|
||||
int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
|
||||
int32_t result = writer_->WriteBytes(b.buffer.get(), b.size);
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -147,8 +155,8 @@ struct ZipFileWriter : public IArchiveWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finishEntry() override {
|
||||
int32_t result = mWriter->FinishEntry();
|
||||
bool FinishEntry() override {
|
||||
int32_t result = writer_->FinishEntry();
|
||||
if (result != 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -156,28 +164,34 @@ struct ZipFileWriter : public IArchiveWriter {
|
||||
}
|
||||
|
||||
virtual ~ZipFileWriter() {
|
||||
if (mWriter) {
|
||||
mWriter->Finish();
|
||||
if (writer_) {
|
||||
writer_->Finish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ZipFileWriter);
|
||||
|
||||
std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
|
||||
std::unique_ptr<ZipWriter> writer_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
|
||||
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path) {
|
||||
std::unique_ptr<DirectoryWriter> writer =
|
||||
util::make_unique<DirectoryWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
if (!writer->Open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
}
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
|
||||
std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path) {
|
||||
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
|
||||
if (!writer->open(diag, path)) {
|
||||
if (!writer->Open(diag, path)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(writer);
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
#ifndef AAPT_FLATTEN_ARCHIVE_H
|
||||
#define AAPT_FLATTEN_ARCHIVE_H
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ArchiveEntry {
|
||||
@@ -38,28 +39,28 @@ struct ArchiveEntry {
|
||||
|
||||
std::string path;
|
||||
uint32_t flags;
|
||||
size_t uncompressedSize;
|
||||
size_t uncompressed_size;
|
||||
};
|
||||
|
||||
class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
|
||||
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);
|
||||
return WriteEntry(buffer, size);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(
|
||||
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path);
|
||||
|
||||
std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(
|
||||
std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
|
||||
IDiagnostics* diag, const StringPiece& path);
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,62 +17,62 @@
|
||||
#ifndef AAPT_FLATTEN_CHUNKWRITER_H
|
||||
#define AAPT_FLATTEN_CHUNKWRITER_H
|
||||
|
||||
#include "android-base/macros.h"
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class ChunkWriter {
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
size_t mStartSize = 0;
|
||||
android::ResChunk_header* mHeader = nullptr;
|
||||
|
||||
public:
|
||||
explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {}
|
||||
|
||||
ChunkWriter(const ChunkWriter&) = delete;
|
||||
ChunkWriter& operator=(const ChunkWriter&) = delete;
|
||||
explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {}
|
||||
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));
|
||||
inline T* StartChunk(uint16_t type) {
|
||||
start_size_ = buffer_->size();
|
||||
T* chunk = buffer_->NextBlock<T>();
|
||||
header_ = &chunk->header;
|
||||
header_->type = util::HostToDevice16(type);
|
||||
header_->headerSize = util::HostToDevice16(sizeof(T));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T* nextBlock(size_t count = 1) {
|
||||
return mBuffer->nextBlock<T>(count);
|
||||
inline T* NextBlock(size_t count = 1) {
|
||||
return buffer_->NextBlock<T>(count);
|
||||
}
|
||||
|
||||
inline BigBuffer* getBuffer() { return mBuffer; }
|
||||
inline BigBuffer* buffer() { return buffer_; }
|
||||
|
||||
inline android::ResChunk_header* getChunkHeader() { return mHeader; }
|
||||
inline android::ResChunk_header* chunk_header() { return header_; }
|
||||
|
||||
inline size_t size() { return mBuffer->size() - mStartSize; }
|
||||
inline size_t size() { return buffer_->size() - start_size_; }
|
||||
|
||||
inline android::ResChunk_header* finish() {
|
||||
mBuffer->align4();
|
||||
mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize);
|
||||
return mHeader;
|
||||
inline android::ResChunk_header* Finish() {
|
||||
buffer_->Align4();
|
||||
header_->size = util::HostToDevice32(buffer_->size() - start_size_);
|
||||
return header_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ChunkWriter);
|
||||
|
||||
BigBuffer* buffer_;
|
||||
size_t start_size_ = 0;
|
||||
android::ResChunk_header* header_ = nullptr;
|
||||
};
|
||||
|
||||
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;
|
||||
inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) {
|
||||
start_size_ = buffer_->size();
|
||||
header_ = buffer_->NextBlock<android::ResChunk_header>();
|
||||
header_->type = util::HostToDevice16(type);
|
||||
header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header));
|
||||
return header_;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
|
||||
@@ -14,21 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "flatten/ChunkWriter.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "flatten/TableFlattener.h"
|
||||
#include "util/BigBuffer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "flatten/ChunkWriter.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "util/BigBuffer.h"
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace aapt {
|
||||
@@ -36,7 +38,7 @@ namespace aapt {
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
static bool cmpIds(const T* a, const T* b) {
|
||||
static bool cmp_ids(const T* a, const T* b) {
|
||||
return a->id.value() < b->id.value();
|
||||
}
|
||||
|
||||
@@ -46,14 +48,14 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
|
||||
}
|
||||
|
||||
size_t i;
|
||||
const char16_t* srcData = src.data();
|
||||
const char16_t* src_data = src.data();
|
||||
for (i = 0; i < len - 1 && i < src.size(); i++) {
|
||||
dst[i] = util::hostToDevice16((uint16_t)srcData[i]);
|
||||
dst[i] = util::HostToDevice16((uint16_t)src_data[i]);
|
||||
}
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
|
||||
static bool cmp_style_entries(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();
|
||||
@@ -70,75 +72,75 @@ struct FlatEntry {
|
||||
Value* value;
|
||||
|
||||
// The entry string pool index to the entry's name.
|
||||
uint32_t entryKey;
|
||||
uint32_t entry_key;
|
||||
};
|
||||
|
||||
class MapFlattenVisitor : public RawValueVisitor {
|
||||
public:
|
||||
using RawValueVisitor::visit;
|
||||
using RawValueVisitor::Visit;
|
||||
|
||||
MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer)
|
||||
: mOutEntry(outEntry), mBuffer(buffer) {}
|
||||
MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
|
||||
: out_entry_(out_entry), buffer_(buffer) {}
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
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);
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
|
||||
FlattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
if (attr->minInt != std::numeric_limits<int32_t>::min()) {
|
||||
if (attr->min_int != 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);
|
||||
static_cast<uint32_t>(attr->min_int));
|
||||
FlattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
|
||||
if (attr->max_int != 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);
|
||||
static_cast<uint32_t>(attr->max_int));
|
||||
FlattenEntry(&key, &val);
|
||||
}
|
||||
|
||||
for (Attribute::Symbol& s : attr->symbols) {
|
||||
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
|
||||
flattenEntry(&s.symbol, &val);
|
||||
FlattenEntry(&s.symbol, &val);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Style* style) override {
|
||||
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);
|
||||
const Reference& parent_ref = style->parent.value();
|
||||
CHECK(bool(parent_ref.id)) << "parent has no ID";
|
||||
out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id);
|
||||
}
|
||||
|
||||
// Sort the style.
|
||||
std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
|
||||
std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
flattenEntry(&entry.key, entry.value.get());
|
||||
FlattenEntry(&entry.key, entry.value.get());
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Styleable* styleable) override {
|
||||
for (auto& attrRef : styleable->entries) {
|
||||
void Visit(Styleable* styleable) override {
|
||||
for (auto& attr_ref : styleable->entries) {
|
||||
BinaryPrimitive val(Res_value{});
|
||||
flattenEntry(&attrRef, &val);
|
||||
FlattenEntry(&attr_ref, &val);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Array* array) override {
|
||||
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++;
|
||||
ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
|
||||
FlattenValue(item.get(), out_entry);
|
||||
out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
|
||||
entry_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
void visit(Plural* plural) override {
|
||||
void Visit(Plural* plural) override {
|
||||
const size_t count = plural->values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!plural->values[i]) {
|
||||
@@ -172,12 +174,12 @@ class MapFlattenVisitor : public RawValueVisitor {
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
LOG(FATAL) << "unhandled plural type";
|
||||
break;
|
||||
}
|
||||
|
||||
Reference key(q);
|
||||
flattenEntry(&key, plural->values[i].get());
|
||||
FlattenEntry(&key, plural->values[i].get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,267 +187,264 @@ class MapFlattenVisitor : public RawValueVisitor {
|
||||
* 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 Finish() { out_entry_->count = util::HostToDevice32(entry_count_); }
|
||||
|
||||
private:
|
||||
void flattenKey(Reference* key, ResTable_map* outEntry) {
|
||||
assert(key->id && "key has no ID");
|
||||
outEntry->name.ident = util::hostToDevice32(key->id.value().id);
|
||||
DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
|
||||
|
||||
void FlattenKey(Reference* key, ResTable_map* out_entry) {
|
||||
CHECK(bool(key->id)) << "key has no ID";
|
||||
out_entry->name.ident = util::HostToDevice32(key->id.value().id);
|
||||
}
|
||||
|
||||
void flattenValue(Item* value, ResTable_map* outEntry) {
|
||||
bool result = value->flatten(&outEntry->value);
|
||||
assert(result && "flatten failed");
|
||||
void FlattenValue(Item* value, ResTable_map* out_entry) {
|
||||
CHECK(value->Flatten(&out_entry->value)) << "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++;
|
||||
void FlattenEntry(Reference* key, Item* value) {
|
||||
ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
|
||||
FlattenKey(key, out_entry);
|
||||
FlattenValue(value, out_entry);
|
||||
out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
|
||||
entry_count_++;
|
||||
}
|
||||
|
||||
ResTable_entry_ext* mOutEntry;
|
||||
BigBuffer* mBuffer;
|
||||
size_t mEntryCount = 0;
|
||||
ResTable_entry_ext* out_entry_;
|
||||
BigBuffer* buffer_;
|
||||
size_t entry_count_ = 0;
|
||||
};
|
||||
|
||||
class PackageFlattener {
|
||||
public:
|
||||
PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
|
||||
: mDiag(diag), mPackage(package) {}
|
||||
: diag_(diag), package_(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());
|
||||
bool FlattenPackage(BigBuffer* buffer) {
|
||||
ChunkWriter pkg_writer(buffer);
|
||||
ResTable_package* pkg_header =
|
||||
pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
|
||||
pkg_header->id = util::HostToDevice32(package_->id.value());
|
||||
|
||||
if (mPackage->name.size() >= arraysize(pkgHeader->name)) {
|
||||
mDiag->error(DiagMessage() << "package name '" << mPackage->name
|
||||
if (package_->name.size() >= arraysize(pkg_header->name)) {
|
||||
diag_->Error(DiagMessage() << "package name '" << package_->name
|
||||
<< "' is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the package name in device endianness.
|
||||
strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name),
|
||||
util::utf8ToUtf16(mPackage->name));
|
||||
strcpy16_htod(pkg_header->name, arraysize(pkg_header->name),
|
||||
util::Utf8ToUtf16(package_->name));
|
||||
|
||||
// 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);
|
||||
BigBuffer type_buffer(1024);
|
||||
FlattenTypes(&type_buffer);
|
||||
|
||||
pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
|
||||
pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
|
||||
StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_);
|
||||
|
||||
pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
|
||||
StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool);
|
||||
pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
|
||||
StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_);
|
||||
|
||||
// Append the types.
|
||||
buffer->appendBuffer(std::move(typeBuffer));
|
||||
buffer->AppendBuffer(std::move(type_buffer));
|
||||
|
||||
pkgWriter.finish();
|
||||
pkg_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTablePackage* mPackage;
|
||||
StringPool mTypePool;
|
||||
StringPool mKeyPool;
|
||||
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
|
||||
|
||||
template <typename T, bool IsItem>
|
||||
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
|
||||
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* result = buffer->nextBlock<T>();
|
||||
ResTable_entry* outEntry = (ResTable_entry*)(result);
|
||||
if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
|
||||
T* result = buffer->NextBlock<T>();
|
||||
ResTable_entry* out_entry = (ResTable_entry*)result;
|
||||
if (entry->entry->symbol_status.state == SymbolState::kPublic) {
|
||||
out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
if (entry->value->isWeak()) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_WEAK;
|
||||
if (entry->value->IsWeak()) {
|
||||
out_entry->flags |= ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
if (!IsItem) {
|
||||
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
|
||||
out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
|
||||
}
|
||||
|
||||
outEntry->flags = util::hostToDevice16(outEntry->flags);
|
||||
outEntry->key.index = util::hostToDevice32(entry->entryKey);
|
||||
outEntry->size = util::hostToDevice16(sizeof(T));
|
||||
out_entry->flags = util::HostToDevice16(out_entry->flags);
|
||||
out_entry->key.index = util::HostToDevice32(entry->entry_key);
|
||||
out_entry->size = util::HostToDevice16(sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
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));
|
||||
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>();
|
||||
CHECK(item->Flatten(outValue)) << "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();
|
||||
ResTable_entry_ext* out_entry =
|
||||
WriteEntry<ResTable_entry_ext, false>(entry, buffer);
|
||||
MapFlattenVisitor visitor(out_entry, buffer);
|
||||
entry->value->Accept(&visitor);
|
||||
visitor.Finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flattenConfig(const ResourceTableType* type,
|
||||
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();
|
||||
ChunkWriter type_writer(buffer);
|
||||
ResTable_type* type_header =
|
||||
type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
|
||||
type_header->id = type->id.value();
|
||||
type_header->config = config;
|
||||
type_header->config.swapHtoD();
|
||||
|
||||
auto maxAccum = [](uint32_t max,
|
||||
const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
|
||||
auto max_accum = [](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 =
|
||||
const uint32_t entry_count =
|
||||
std::accumulate(type->entries.begin(), type->entries.end(), 0,
|
||||
maxAccum) +
|
||||
max_accum) +
|
||||
1;
|
||||
|
||||
typeHeader->entryCount = util::hostToDevice32(entryCount);
|
||||
uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
|
||||
type_header->entryCount = util::HostToDevice32(entry_count);
|
||||
uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count);
|
||||
|
||||
assert((size_t)entryCount <= std::numeric_limits<uint16_t>::max() + 1);
|
||||
memset(indices, 0xff, entryCount * sizeof(uint32_t));
|
||||
CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max());
|
||||
memset(indices, 0xff, entry_count * sizeof(uint32_t));
|
||||
|
||||
typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
|
||||
type_header->entriesStart = util::HostToDevice32(type_writer.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()
|
||||
const size_t entry_start = type_writer.buffer()->size();
|
||||
for (FlatEntry& flat_entry : *entries) {
|
||||
CHECK(flat_entry.entry->id.value() < entry_count);
|
||||
indices[flat_entry.entry->id.value()] =
|
||||
util::HostToDevice32(type_writer.buffer()->size() - entry_start);
|
||||
if (!FlattenValue(&flat_entry, type_writer.buffer())) {
|
||||
diag_->Error(DiagMessage()
|
||||
<< "failed to flatten resource '"
|
||||
<< ResourceNameRef(mPackage->name, type->type,
|
||||
flatEntry.entry->name)
|
||||
<< ResourceNameRef(package_->name, type->type,
|
||||
flat_entry.entry->name)
|
||||
<< "' for configuration '" << config << "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
typeWriter.finish();
|
||||
type_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ResourceTableType*> collectAndSortTypes() {
|
||||
std::vector<ResourceTableType*> sortedTypes;
|
||||
for (auto& type : mPackage->types) {
|
||||
std::vector<ResourceTableType*> CollectAndSortTypes() {
|
||||
std::vector<ResourceTableType*> sorted_types;
|
||||
for (auto& type : package_->types) {
|
||||
if (type->type == ResourceType::kStyleable) {
|
||||
// Styleables aren't real Resource Types, they are represented in the
|
||||
// R.java
|
||||
// file.
|
||||
// R.java file.
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(type->id && "type must have an ID set");
|
||||
CHECK(bool(type->id)) << "type must have an ID set";
|
||||
|
||||
sortedTypes.push_back(type.get());
|
||||
sorted_types.push_back(type.get());
|
||||
}
|
||||
std::sort(sortedTypes.begin(), sortedTypes.end(),
|
||||
cmpIds<ResourceTableType>);
|
||||
return sortedTypes;
|
||||
std::sort(sorted_types.begin(), sorted_types.end(),
|
||||
cmp_ids<ResourceTableType>);
|
||||
return sorted_types;
|
||||
}
|
||||
|
||||
std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
|
||||
std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
|
||||
// Sort the entries by entry ID.
|
||||
std::vector<ResourceEntry*> sortedEntries;
|
||||
std::vector<ResourceEntry*> sorted_entries;
|
||||
for (auto& entry : type->entries) {
|
||||
assert(entry->id && "entry must have an ID set");
|
||||
sortedEntries.push_back(entry.get());
|
||||
CHECK(bool(entry->id)) << "entry must have an ID set";
|
||||
sorted_entries.push_back(entry.get());
|
||||
}
|
||||
std::sort(sortedEntries.begin(), sortedEntries.end(),
|
||||
cmpIds<ResourceEntry>);
|
||||
return sortedEntries;
|
||||
std::sort(sorted_entries.begin(), sorted_entries.end(),
|
||||
cmp_ids<ResourceEntry>);
|
||||
return sorted_entries;
|
||||
}
|
||||
|
||||
bool flattenTypeSpec(ResourceTableType* type,
|
||||
std::vector<ResourceEntry*>* sortedEntries,
|
||||
bool FlattenTypeSpec(ResourceTableType* type,
|
||||
std::vector<ResourceEntry*>* sorted_entries,
|
||||
BigBuffer* buffer) {
|
||||
ChunkWriter typeSpecWriter(buffer);
|
||||
ResTable_typeSpec* specHeader =
|
||||
typeSpecWriter.startChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
|
||||
specHeader->id = type->id.value();
|
||||
ChunkWriter type_spec_writer(buffer);
|
||||
ResTable_typeSpec* spec_header =
|
||||
type_spec_writer.StartChunk<ResTable_typeSpec>(
|
||||
RES_TABLE_TYPE_SPEC_TYPE);
|
||||
spec_header->id = type->id.value();
|
||||
|
||||
if (sortedEntries->empty()) {
|
||||
typeSpecWriter.finish();
|
||||
if (sorted_entries->empty()) {
|
||||
type_spec_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
const size_t num_entries = sorted_entries->back()->id.value() + 1;
|
||||
|
||||
specHeader->entryCount = util::hostToDevice32(numEntries);
|
||||
spec_header->entryCount = util::HostToDevice32(num_entries);
|
||||
|
||||
// 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);
|
||||
uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
|
||||
|
||||
const size_t actualNumEntries = sortedEntries->size();
|
||||
for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
|
||||
ResourceEntry* entry = sortedEntries->at(entryIndex);
|
||||
const size_t actual_num_entries = sorted_entries->size();
|
||||
for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
|
||||
ResourceEntry* entry = sorted_entries->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);
|
||||
if (entry->symbol_status.state == SymbolState::kPublic) {
|
||||
config_masks[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 size_t config_count = entry->values.size();
|
||||
for (size_t i = 0; i < config_count; 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));
|
||||
for (size_t j = i + 1; j < config_count; j++) {
|
||||
config_masks[entry->id.value()] |=
|
||||
util::HostToDevice32(config.diff(entry->values[j]->config));
|
||||
}
|
||||
}
|
||||
}
|
||||
typeSpecWriter.finish();
|
||||
type_spec_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flattenTypes(BigBuffer* buffer) {
|
||||
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();
|
||||
std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes();
|
||||
|
||||
size_t expectedTypeId = 1;
|
||||
for (ResourceTableType* type : sortedTypes) {
|
||||
size_t expected_type_id = 1;
|
||||
for (ResourceTableType* type : sorted_types) {
|
||||
// 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++;
|
||||
while (type->id.value() > expected_type_id) {
|
||||
std::stringstream type_name;
|
||||
type_name << "?" << expected_type_id;
|
||||
type_pool_.MakeRef(type_name.str());
|
||||
expected_type_id++;
|
||||
}
|
||||
expectedTypeId++;
|
||||
mTypePool.makeRef(toString(type->type));
|
||||
expected_type_id++;
|
||||
type_pool_.MakeRef(ToString(type->type));
|
||||
|
||||
std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
|
||||
std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
|
||||
|
||||
if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
|
||||
if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -455,35 +454,41 @@ class PackageFlattener {
|
||||
// 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();
|
||||
std::map<ConfigDescription, std::vector<FlatEntry>>
|
||||
config_to_entry_list_map;
|
||||
for (ResourceEntry* entry : sorted_entries) {
|
||||
const uint32_t key_index =
|
||||
(uint32_t)key_pool_.MakeRef(entry->name).index();
|
||||
|
||||
// Group values by configuration.
|
||||
for (auto& configValue : entry->values) {
|
||||
configToEntryListMap[configValue->config].push_back(
|
||||
FlatEntry{entry, configValue->value.get(), keyIndex});
|
||||
for (auto& config_value : entry->values) {
|
||||
config_to_entry_list_map[config_value->config].push_back(
|
||||
FlatEntry{entry, config_value->value.get(), key_index});
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten a configuration value.
|
||||
for (auto& entry : configToEntryListMap) {
|
||||
if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
|
||||
for (auto& entry : config_to_entry_list_map) {
|
||||
if (!FlattenConfig(type, entry.first, &entry.second, buffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IDiagnostics* diag_;
|
||||
ResourceTablePackage* package_;
|
||||
StringPool type_pool_;
|
||||
StringPool key_pool_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
|
||||
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(
|
||||
table->string_pool.Sort(
|
||||
[](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
int diff = a.context.priority - b.context.priority;
|
||||
if (diff < 0) return true;
|
||||
@@ -493,30 +498,30 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
|
||||
if (diff > 0) return false;
|
||||
return a.value < b.value;
|
||||
});
|
||||
table->stringPool.prune();
|
||||
table->string_pool.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());
|
||||
ChunkWriter table_writer(buffer_);
|
||||
ResTable_header* table_header =
|
||||
table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
|
||||
table_header->packageCount = util::HostToDevice32(table->packages.size());
|
||||
|
||||
// Flatten the values string pool.
|
||||
StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
|
||||
StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
|
||||
|
||||
BigBuffer packageBuffer(1024);
|
||||
BigBuffer package_buffer(1024);
|
||||
|
||||
// Flatten each package.
|
||||
for (auto& package : table->packages) {
|
||||
PackageFlattener flattener(context->getDiagnostics(), package.get());
|
||||
if (!flattener.flattenPackage(&packageBuffer)) {
|
||||
PackageFlattener flattener(context->GetDiagnostics(), package.get());
|
||||
if (!flattener.FlattenPackage(&package_buffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally merge all the packages into the main buffer.
|
||||
tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
|
||||
tableWriter.finish();
|
||||
table_writer.buffer()->AppendBuffer(std::move(package_buffer));
|
||||
table_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,21 +17,24 @@
|
||||
#ifndef AAPT_FLATTEN_TABLEFLATTENER_H
|
||||
#define AAPT_FLATTEN_TABLEFLATTENER_H
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/BigBuffer.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class BigBuffer;
|
||||
class ResourceTable;
|
||||
|
||||
class TableFlattener : public IResourceTableConsumer {
|
||||
public:
|
||||
explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {}
|
||||
explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {}
|
||||
|
||||
bool consume(IAaptContext* context, ResourceTable* table) override;
|
||||
bool Consume(IAaptContext* context, ResourceTable* table) override;
|
||||
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
DISALLOW_COPY_AND_ASSIGN(TableFlattener);
|
||||
|
||||
BigBuffer* buffer_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "flatten/TableFlattener.h"
|
||||
|
||||
#include "ResourceUtils.h"
|
||||
#include "test/Test.h"
|
||||
#include "unflatten/BinaryResourceParser.h"
|
||||
@@ -27,162 +28,165 @@ namespace aapt {
|
||||
class TableFlattenerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext = test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setPackageId(0x7f)
|
||||
.build();
|
||||
context_ = test::ContextBuilder()
|
||||
.SetCompilationPackage("com.app.test")
|
||||
.SetPackageId(0x7f)
|
||||
.Build();
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
|
||||
::testing::AssertionResult Flatten(ResourceTable* table,
|
||||
ResTable* out_table) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
if (!flattener.Consume(context_.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) {
|
||||
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
|
||||
if (out_table->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) {
|
||||
::testing::AssertionResult Flatten(ResourceTable* table,
|
||||
ResourceTable* out_table) {
|
||||
BigBuffer buffer(1024);
|
||||
TableFlattener flattener(&buffer);
|
||||
if (!flattener.consume(mContext.get(), table)) {
|
||||
if (!flattener.Consume(context_.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(),
|
||||
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
|
||||
BinaryResourceParser parser(context_.get(), out_table, {}, data.get(),
|
||||
buffer.size());
|
||||
if (!parser.parse()) {
|
||||
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);
|
||||
::testing::AssertionResult Exists(ResTable* table,
|
||||
const StringPiece& expected_name,
|
||||
const ResourceId& expected_id,
|
||||
const ConfigDescription& expected_config,
|
||||
const uint8_t expected_data_type,
|
||||
const uint32_t expected_data,
|
||||
const uint32_t expected_spec_flags) {
|
||||
const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
|
||||
|
||||
table->setParameters(&expectedConfig);
|
||||
table->setParameters(&expected_config);
|
||||
|
||||
ResTable_config config;
|
||||
Res_value val;
|
||||
uint32_t specFlags;
|
||||
if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) <
|
||||
0) {
|
||||
uint32_t spec_flags;
|
||||
if (table->getResource(expected_id.id, &val, false, 0, &spec_flags,
|
||||
&config) < 0) {
|
||||
return ::testing::AssertionFailure() << "could not find resource with";
|
||||
}
|
||||
|
||||
if (expectedDataType != val.dataType) {
|
||||
if (expected_data_type != val.dataType) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data type " << std::hex << (int)expectedDataType
|
||||
<< "expected data type " << std::hex << (int)expected_data_type
|
||||
<< " but got data type " << (int)val.dataType << std::dec
|
||||
<< " instead";
|
||||
}
|
||||
|
||||
if (expectedData != val.data) {
|
||||
if (expected_data != val.data) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected data " << std::hex << expectedData << " but got data "
|
||||
<< val.data << std::dec << " instead";
|
||||
<< "expected data " << std::hex << expected_data
|
||||
<< " but got data " << val.data << std::dec << " instead";
|
||||
}
|
||||
|
||||
if (expectedSpecFlags != specFlags) {
|
||||
if (expected_spec_flags != spec_flags) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "expected specFlags " << std::hex << expectedSpecFlags
|
||||
<< " but got specFlags " << specFlags << std::dec << " instead";
|
||||
<< "expected specFlags " << std::hex << expected_spec_flags
|
||||
<< " but got specFlags " << spec_flags << std::dec << " instead";
|
||||
}
|
||||
|
||||
ResTable::resource_name actualName;
|
||||
if (!table->getResourceName(expectedId.id, false, &actualName)) {
|
||||
ResTable::resource_name actual_name;
|
||||
if (!table->getResourceName(expected_id.id, false, &actual_name)) {
|
||||
return ::testing::AssertionFailure() << "failed to find resource name";
|
||||
}
|
||||
|
||||
Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName);
|
||||
Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
|
||||
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) << "'";
|
||||
<< "expected name '" << expected_res_name << "' but got '"
|
||||
<< StringPiece16(actual_name.package, actual_name.packageLen)
|
||||
<< ":" << StringPiece16(actual_name.type, actual_name.typeLen)
|
||||
<< "/" << StringPiece16(actual_name.name, actual_name.nameLen)
|
||||
<< "'";
|
||||
}
|
||||
|
||||
if (expectedConfig != config) {
|
||||
if (expected_config != config) {
|
||||
return ::testing::AssertionFailure() << "expected config '"
|
||||
<< expectedConfig << "' but got '"
|
||||
<< expected_config << "' but got '"
|
||||
<< ConfigDescription(config) << "'";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
std::unique_ptr<IAaptContext> context_;
|
||||
};
|
||||
|
||||
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",
|
||||
.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),
|
||||
.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"),
|
||||
.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),
|
||||
.AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
|
||||
.AddString("com.app.test:layout/bar", ResourceId(0x7f050000),
|
||||
"res/layout/bar.xml")
|
||||
.build();
|
||||
.Build();
|
||||
|
||||
ResTable resTable;
|
||||
ASSERT_TRUE(flatten(table.get(), &resTable));
|
||||
ResTable res_table;
|
||||
ASSERT_TRUE(Flatten(table.get(), &res_table));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000),
|
||||
EXPECT_TRUE(Exists(&res_table, "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),
|
||||
EXPECT_TRUE(Exists(&res_table, "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(&res_table, "com.app.test:id/three",
|
||||
ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE,
|
||||
0x7f020000u, 0u));
|
||||
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one",
|
||||
EXPECT_TRUE(Exists(&res_table, "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"),
|
||||
EXPECT_TRUE(Exists(&res_table, "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());
|
||||
std::u16string foo_str = u"foo";
|
||||
ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(),
|
||||
foo_str.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:string/test",
|
||||
EXPECT_TRUE(Exists(&res_table, "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());
|
||||
std::u16string bar_path = u"res/layout/bar.xml";
|
||||
idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(),
|
||||
bar_path.size());
|
||||
ASSERT_GE(idx, 0);
|
||||
EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar",
|
||||
EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar",
|
||||
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING,
|
||||
(uint32_t)idx, 0u));
|
||||
}
|
||||
@@ -190,42 +194,43 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
|
||||
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();
|
||||
.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 res_table;
|
||||
ASSERT_TRUE(Flatten(table.get(), &res_table));
|
||||
|
||||
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),
|
||||
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
|
||||
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
|
||||
EXPECT_TRUE(Exists(&res_table, "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;
|
||||
attr.type_mask = android::ResTable_map::TYPE_INTEGER;
|
||||
attr.min_int = 10;
|
||||
attr.max_int = 23;
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.setPackageId("android", 0x01)
|
||||
.addValue("android:attr/foo", ResourceId(0x01010000),
|
||||
.SetPackageId("android", 0x01)
|
||||
.AddValue("android:attr/foo", ResourceId(0x01010000),
|
||||
util::make_unique<Attribute>(attr))
|
||||
.build();
|
||||
.Build();
|
||||
|
||||
ResourceTable result;
|
||||
ASSERT_TRUE(flatten(table.get(), &result));
|
||||
ASSERT_TRUE(Flatten(table.get(), &result));
|
||||
|
||||
Attribute* actualAttr =
|
||||
test::getValue<Attribute>(&result, "android:attr/foo");
|
||||
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);
|
||||
EXPECT_EQ(attr.IsWeak(), actualAttr->IsWeak());
|
||||
EXPECT_EQ(attr.type_mask, actualAttr->type_mask);
|
||||
EXPECT_EQ(attr.min_int, actualAttr->min_int);
|
||||
EXPECT_EQ(attr.max_int, actualAttr->max_int);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,17 +15,21 @@
|
||||
*/
|
||||
|
||||
#include "flatten/XmlFlattener.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
#include "android-base/macros.h"
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
#include "utils/misc.h"
|
||||
|
||||
#include "SdkConstants.h"
|
||||
#include "flatten/ChunkWriter.h"
|
||||
#include "flatten/ResourceTypeExtensions.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <utils/misc.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace aapt {
|
||||
@@ -34,216 +38,215 @@ namespace {
|
||||
|
||||
constexpr uint32_t kLowPriority = 0xffffffffu;
|
||||
|
||||
struct XmlFlattenerVisitor : public xml::Visitor {
|
||||
using xml::Visitor::visit;
|
||||
static bool cmp_xml_attribute_by_id(const xml::Attribute* a,
|
||||
const xml::Attribute* b) {
|
||||
if (a->compiled_attribute && a->compiled_attribute.value().id) {
|
||||
if (b->compiled_attribute && b->compiled_attribute.value().id) {
|
||||
return a->compiled_attribute.value().id.value() <
|
||||
b->compiled_attribute.value().id.value();
|
||||
}
|
||||
return true;
|
||||
} else if (!b->compiled_attribute) {
|
||||
int diff = a->namespace_uri.compare(b->namespace_uri);
|
||||
if (diff < 0) {
|
||||
return true;
|
||||
} else if (diff > 0) {
|
||||
return false;
|
||||
}
|
||||
return a->name < b->name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
StringPool mPool;
|
||||
std::map<uint8_t, StringPool> mPackagePools;
|
||||
class XmlFlattenerVisitor : public xml::Visitor {
|
||||
public:
|
||||
using xml::Visitor::Visit;
|
||||
|
||||
StringPool pool;
|
||||
std::map<uint8_t, StringPool> package_pools;
|
||||
|
||||
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;
|
||||
std::vector<StringFlattenDest> string_refs;
|
||||
|
||||
XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
|
||||
: mBuffer(buffer), mOptions(options) {}
|
||||
: buffer_(buffer), options_(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) {
|
||||
void Visit(xml::Namespace* node) override {
|
||||
if (node->namespace_uri == xml::kSchemaTools) {
|
||||
// Skip dedicated tools namespace.
|
||||
xml::Visitor::visit(node);
|
||||
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);
|
||||
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()) {
|
||||
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);
|
||||
ChunkWriter writer(buffer_);
|
||||
ResXMLTree_node* flat_node =
|
||||
writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
|
||||
flat_node->lineNumber = util::HostToDevice32(node->line_number);
|
||||
flat_node->comment.index = util::HostToDevice32(-1);
|
||||
|
||||
ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
|
||||
addString(node->text, kLowPriority, &flatText->data);
|
||||
ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
|
||||
AddString(node->text, kLowPriority, &flat_text->data);
|
||||
|
||||
writer.finish();
|
||||
writer.Finish();
|
||||
}
|
||||
|
||||
void visit(xml::Element* node) override {
|
||||
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);
|
||||
ChunkWriter start_writer(buffer_);
|
||||
ResXMLTree_node* flat_node =
|
||||
start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
|
||||
flat_node->lineNumber = util::HostToDevice32(node->line_number);
|
||||
flat_node->comment.index = util::HostToDevice32(-1);
|
||||
|
||||
ResXMLTree_attrExt* flatElem =
|
||||
startWriter.nextBlock<ResXMLTree_attrExt>();
|
||||
ResXMLTree_attrExt* flat_elem =
|
||||
start_writer.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 */);
|
||||
// runtime complains.
|
||||
AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
|
||||
true /* treat_empty_string_as_null */);
|
||||
AddString(node->name, kLowPriority, &flat_elem->name,
|
||||
true /* treat_empty_string_as_null */);
|
||||
|
||||
flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
|
||||
flatElem->attributeSize =
|
||||
util::hostToDevice16(sizeof(ResXMLTree_attribute));
|
||||
flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
|
||||
flat_elem->attributeSize =
|
||||
util::HostToDevice16(sizeof(ResXMLTree_attribute));
|
||||
|
||||
writeAttributes(node, flatElem, &startWriter);
|
||||
WriteAttributes(node, flat_elem, &start_writer);
|
||||
|
||||
startWriter.finish();
|
||||
start_writer.Finish();
|
||||
}
|
||||
|
||||
xml::Visitor::visit(node);
|
||||
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);
|
||||
ChunkWriter end_writer(buffer_);
|
||||
ResXMLTree_node* flat_end_node =
|
||||
end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
|
||||
flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
|
||||
flat_end_node->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);
|
||||
ResXMLTree_endElementExt* flat_end_elem =
|
||||
end_writer.NextBlock<ResXMLTree_endElementExt>();
|
||||
AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
|
||||
true /* treat_empty_string_as_null */);
|
||||
AddString(node->name, kLowPriority, &flat_end_elem->name);
|
||||
|
||||
endWriter.finish();
|
||||
end_writer.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;
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
|
||||
|
||||
void AddString(const StringPiece& str, uint32_t priority,
|
||||
android::ResStringPool_ref* dest,
|
||||
bool treat_empty_string_as_null = false) {
|
||||
if (str.empty() && treat_empty_string_as_null) {
|
||||
// Some parts of the runtime treat null differently than empty string.
|
||||
dest->index = util::DeviceToHost32(-1);
|
||||
} else {
|
||||
string_refs.push_back(StringFlattenDest{
|
||||
pool.MakeRef(str, StringPool::Context(priority)), dest});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem,
|
||||
void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
|
||||
string_refs.push_back(StringFlattenDest{ref, dest});
|
||||
}
|
||||
|
||||
void WriteNamespace(xml::Namespace* node, uint16_t type) {
|
||||
ChunkWriter writer(buffer_);
|
||||
|
||||
ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
|
||||
flatNode->lineNumber = util::HostToDevice32(node->line_number);
|
||||
flatNode->comment.index = util::HostToDevice32(-1);
|
||||
|
||||
ResXMLTree_namespaceExt* flat_ns =
|
||||
writer.NextBlock<ResXMLTree_namespaceExt>();
|
||||
AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix);
|
||||
AddString(node->namespace_uri, kLowPriority, &flat_ns->uri);
|
||||
|
||||
writer.Finish();
|
||||
}
|
||||
|
||||
void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem,
|
||||
ChunkWriter* writer) {
|
||||
mFilteredAttrs.clear();
|
||||
mFilteredAttrs.reserve(node->attributes.size());
|
||||
filtered_attrs_.clear();
|
||||
filtered_attrs_.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()) {
|
||||
if (options_.max_sdk_level && attr.compiled_attribute &&
|
||||
attr.compiled_attribute.value().id) {
|
||||
size_t sdk_level =
|
||||
FindAttributeSdkLevel(attr.compiled_attribute.value().id.value());
|
||||
if (sdk_level > options_.max_sdk_level.value()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (attr.namespaceUri == xml::kSchemaTools) {
|
||||
if (attr.namespace_uri == xml::kSchemaTools) {
|
||||
continue;
|
||||
}
|
||||
mFilteredAttrs.push_back(&attr);
|
||||
filtered_attrs_.push_back(&attr);
|
||||
}
|
||||
|
||||
if (mFilteredAttrs.empty()) {
|
||||
if (filtered_attrs_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResourceId kIdAttr(0x010100d0);
|
||||
|
||||
std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(),
|
||||
cmpXmlAttributeById);
|
||||
std::sort(filtered_attrs_.begin(), filtered_attrs_.end(),
|
||||
cmp_xml_attribute_by_id);
|
||||
|
||||
flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
|
||||
flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
|
||||
|
||||
ResXMLTree_attribute* flatAttr =
|
||||
writer->nextBlock<ResXMLTree_attribute>(mFilteredAttrs.size());
|
||||
uint16_t attributeIndex = 1;
|
||||
for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
|
||||
ResXMLTree_attribute* flat_attr =
|
||||
writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
|
||||
uint16_t attribute_index = 1;
|
||||
for (const xml::Attribute* xml_attr : filtered_attrs_) {
|
||||
// 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);
|
||||
if (xml_attr->compiled_attribute &&
|
||||
xml_attr->compiled_attribute.value().id &&
|
||||
xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
|
||||
flat_elem->idIndex = util::HostToDevice16(attribute_index);
|
||||
} else if (xml_attr->namespace_uri.empty()) {
|
||||
if (xml_attr->name == "class") {
|
||||
flat_elem->classIndex = util::HostToDevice16(attribute_index);
|
||||
} else if (xml_attr->name == "style") {
|
||||
flat_elem->styleIndex = util::HostToDevice16(attribute_index);
|
||||
}
|
||||
}
|
||||
attributeIndex++;
|
||||
attribute_index++;
|
||||
|
||||
// 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 */);
|
||||
AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
|
||||
true /* treat_empty_string_as_null */);
|
||||
|
||||
flatAttr->rawValue.index = util::hostToDevice32(-1);
|
||||
flat_attr->rawValue.index = util::HostToDevice32(-1);
|
||||
|
||||
if (!xmlAttr->compiledAttribute ||
|
||||
!xmlAttr->compiledAttribute.value().id) {
|
||||
if (!xml_attr->compiled_attribute ||
|
||||
!xml_attr->compiled_attribute.value().id) {
|
||||
// The attribute has no associated ResourceID, so the string order
|
||||
// doesn't matter.
|
||||
addString(xmlAttr->name, kLowPriority, &flatAttr->name);
|
||||
AddString(xml_attr->name, kLowPriority, &flat_attr->name);
|
||||
} else {
|
||||
// Attribute names are stored without packages, but we use
|
||||
// their StringPool index to lookup their resource IDs.
|
||||
@@ -252,99 +255,106 @@ struct XmlFlattenerVisitor : public xml::Visitor {
|
||||
// pools that we later combine.
|
||||
//
|
||||
// Lookup the StringPool for this package and make the reference there.
|
||||
const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
|
||||
const xml::AaptAttribute& aapt_attr =
|
||||
xml_attr->compiled_attribute.value();
|
||||
|
||||
StringPool::Ref nameRef =
|
||||
mPackagePools[aaptAttr.id.value().packageId()].makeRef(
|
||||
xmlAttr->name, StringPool::Context(aaptAttr.id.value().id));
|
||||
StringPool::Ref name_ref =
|
||||
package_pools[aapt_attr.id.value().package_id()].MakeRef(
|
||||
xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
|
||||
|
||||
// Add it to the list of strings to flatten.
|
||||
addString(nameRef, &flatAttr->name);
|
||||
AddString(name_ref, &flat_attr->name);
|
||||
}
|
||||
|
||||
if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
|
||||
if (options_.keep_raw_values || !xml_attr->compiled_value) {
|
||||
// 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);
|
||||
AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue);
|
||||
}
|
||||
|
||||
if (xmlAttr->compiledValue) {
|
||||
bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
|
||||
assert(result);
|
||||
if (xml_attr->compiled_value) {
|
||||
CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
|
||||
} else {
|
||||
// Flatten as a regular string type.
|
||||
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
addString(xmlAttr->value, kLowPriority,
|
||||
(ResStringPool_ref*)&flatAttr->typedValue.data);
|
||||
flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
AddString(xml_attr->value, kLowPriority,
|
||||
(ResStringPool_ref*)&flat_attr->typedValue.data);
|
||||
}
|
||||
|
||||
flatAttr->typedValue.size =
|
||||
util::hostToDevice16(sizeof(flatAttr->typedValue));
|
||||
flatAttr++;
|
||||
flat_attr->typedValue.size =
|
||||
util::HostToDevice16(sizeof(flat_attr->typedValue));
|
||||
flat_attr++;
|
||||
}
|
||||
}
|
||||
|
||||
BigBuffer* buffer_;
|
||||
XmlFlattenerOptions options_;
|
||||
|
||||
// Scratch vector to filter attributes. We avoid allocations
|
||||
// making this a member.
|
||||
std::vector<xml::Attribute*> filtered_attrs_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
|
||||
BigBuffer nodeBuffer(1024);
|
||||
XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
|
||||
node->accept(&visitor);
|
||||
bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
|
||||
BigBuffer node_buffer(1024);
|
||||
XmlFlattenerVisitor visitor(&node_buffer, options_);
|
||||
node->Accept(&visitor);
|
||||
|
||||
// Merge the package pools into the main pool.
|
||||
for (auto& packagePoolEntry : visitor.mPackagePools) {
|
||||
visitor.mPool.merge(std::move(packagePoolEntry.second));
|
||||
for (auto& package_pool_entry : visitor.package_pools) {
|
||||
visitor.pool.Merge(std::move(package_pool_entry.second));
|
||||
}
|
||||
|
||||
// Sort the string pool so that attribute resource IDs show up first.
|
||||
visitor.mPool.sort(
|
||||
visitor.pool.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());
|
||||
for (const auto& ref_entry : visitor.string_refs) {
|
||||
ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
|
||||
}
|
||||
|
||||
// Write the XML header.
|
||||
ChunkWriter xmlHeaderWriter(mBuffer);
|
||||
xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
|
||||
ChunkWriter xml_header_writer(buffer_);
|
||||
xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
|
||||
|
||||
// Flatten the StringPool.
|
||||
StringPool::flattenUtf8(mBuffer, visitor.mPool);
|
||||
StringPool::FlattenUtf8(buffer_, visitor.pool);
|
||||
|
||||
{
|
||||
// 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) {
|
||||
ChunkWriter res_id_map_writer(buffer_);
|
||||
res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
|
||||
for (const auto& str : visitor.pool) {
|
||||
ResourceId id = {str->context.priority};
|
||||
if (id.id == kLowPriority || !id.isValid()) {
|
||||
if (id.id == kLowPriority || !id.is_valid()) {
|
||||
// When we see the first non-resource ID,
|
||||
// we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
*resIdMapWriter.nextBlock<uint32_t>() = id.id;
|
||||
*res_id_map_writer.NextBlock<uint32_t>() = id.id;
|
||||
}
|
||||
resIdMapWriter.finish();
|
||||
res_id_map_writer.Finish();
|
||||
}
|
||||
|
||||
// Move the nodeBuffer and append it to the out buffer.
|
||||
mBuffer->appendBuffer(std::move(nodeBuffer));
|
||||
buffer_->AppendBuffer(std::move(node_buffer));
|
||||
|
||||
// Finish the xml header.
|
||||
xmlHeaderWriter.finish();
|
||||
xml_header_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
|
||||
bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
|
||||
if (!resource->root) {
|
||||
return false;
|
||||
}
|
||||
return flatten(context, resource->root.get());
|
||||
return Flatten(context, resource->root.get());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef AAPT_FLATTEN_XMLFLATTENER_H
|
||||
#define AAPT_FLATTEN_XMLFLATTENER_H
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "xml/XmlDom.h"
|
||||
@@ -27,26 +29,28 @@ struct XmlFlattenerOptions {
|
||||
/**
|
||||
* Keep attribute raw string values along with typed values.
|
||||
*/
|
||||
bool keepRawValues = false;
|
||||
bool keep_raw_values = false;
|
||||
|
||||
/**
|
||||
* If set, the max SDK level of attribute to flatten. All others are ignored.
|
||||
*/
|
||||
Maybe<size_t> maxSdkLevel;
|
||||
Maybe<size_t> max_sdk_level;
|
||||
};
|
||||
|
||||
class XmlFlattener : public IXmlResourceConsumer {
|
||||
public:
|
||||
XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
|
||||
: mBuffer(buffer), mOptions(options) {}
|
||||
: buffer_(buffer), options_(options) {}
|
||||
|
||||
bool consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
|
||||
private:
|
||||
BigBuffer* mBuffer;
|
||||
XmlFlattenerOptions mOptions;
|
||||
DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
|
||||
|
||||
bool flatten(IAaptContext* context, xml::Node* node);
|
||||
bool Flatten(IAaptContext* context, xml::Node* node);
|
||||
|
||||
BigBuffer* buffer_;
|
||||
XmlFlattenerOptions options_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,61 +15,62 @@
|
||||
*/
|
||||
|
||||
#include "flatten/XmlFlattener.h"
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "link/Linkers.h"
|
||||
#include "test/Test.h"
|
||||
#include "util/BigBuffer.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class XmlFlattenerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
mContext =
|
||||
context_ =
|
||||
test::ContextBuilder()
|
||||
.setCompilationPackage("com.app.test")
|
||||
.setNameManglerPolicy(NameManglerPolicy{"com.app.test"})
|
||||
.addSymbolSource(
|
||||
.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",
|
||||
.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",
|
||||
test::AttributeBuilder().Build())
|
||||
.AddSymbol("android:attr/colorAccent",
|
||||
ResourceId(0x01010435),
|
||||
test::AttributeBuilder().build())
|
||||
.build())
|
||||
.build();
|
||||
test::AttributeBuilder().Build())
|
||||
.Build())
|
||||
.Build();
|
||||
}
|
||||
|
||||
::testing::AssertionResult flatten(xml::XmlResource* doc,
|
||||
android::ResXMLTree* outTree,
|
||||
::testing::AssertionResult Flatten(xml::XmlResource* doc,
|
||||
android::ResXMLTree* out_tree,
|
||||
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)) {
|
||||
if (!flattener.Consume(context_.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) {
|
||||
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
|
||||
if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
|
||||
return ::testing::AssertionFailure() << "flattened XML is corrupt";
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<IAaptContext> mContext;
|
||||
std::unique_ptr<IAaptContext> context_;
|
||||
};
|
||||
|
||||
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" />
|
||||
@@ -77,27 +78,27 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
|
||||
</View>)EOF");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
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");
|
||||
const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
|
||||
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
const char16_t* namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.test");
|
||||
|
||||
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");
|
||||
const char16_t* tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tag_name, 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");
|
||||
const char16_t* attr_name = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attr_name, len), u"attr");
|
||||
|
||||
EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr",
|
||||
StringPiece16(u"attr").size()));
|
||||
@@ -105,22 +106,22 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
|
||||
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");
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tag_name, 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");
|
||||
const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attr_namespace, len), u"http://com.test");
|
||||
|
||||
attrName = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attrName, len), u"hello");
|
||||
attr_name = tree.getAttributeName(0, &len);
|
||||
EXPECT_EQ(StringPiece16(attr_name, len), u"hello");
|
||||
|
||||
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");
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tag_name, len), u"Layout");
|
||||
ASSERT_EQ(0u, tree.getAttributeCount());
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
|
||||
@@ -129,39 +130,39 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
|
||||
|
||||
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");
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tag_name, 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");
|
||||
tag_name = tree.getElementName(&len);
|
||||
EXPECT_EQ(StringPiece16(tag_name, len), u"View");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE);
|
||||
namespacePrefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test");
|
||||
namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespace_prefix, len), u"test");
|
||||
|
||||
namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test");
|
||||
namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespace_uri, len), u"http://com.test");
|
||||
|
||||
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);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
ASSERT_TRUE(linker.sdk_levels().count(17) == 1);
|
||||
ASSERT_TRUE(linker.sdk_levels().count(21) == 1);
|
||||
|
||||
android::ResXMLTree tree;
|
||||
XmlFlattenerOptions options;
|
||||
options.maxSdkLevel = 17;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree, options));
|
||||
options.max_sdk_level = 17;
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree, options));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
@@ -173,23 +174,23 @@ TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
|
||||
}
|
||||
|
||||
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));
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
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");
|
||||
const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
|
||||
EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo");
|
||||
|
||||
const char16_t* namespaceUri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespaceUri, len),
|
||||
const char16_t* namespace_uri = tree.getNamespaceUri(&len);
|
||||
ASSERT_EQ(StringPiece16(namespace_uri, len),
|
||||
u"http://schemas.android.com/foo");
|
||||
|
||||
ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
|
||||
@@ -200,14 +201,14 @@ TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
|
||||
}
|
||||
|
||||
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));
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
@@ -225,10 +226,10 @@ TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
|
||||
*/
|
||||
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
test::buildXmlDom("<View package=\"android\"/>");
|
||||
test::BuildXmlDom("<View package=\"android\"/>");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
@@ -242,10 +243,10 @@ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
|
||||
|
||||
TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
test::buildXmlDom("<View package=\"\"/>");
|
||||
test::BuildXmlDom("<View package=\"\"/>");
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(flatten(doc.get(), &tree));
|
||||
ASSERT_TRUE(Flatten(doc.get(), &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::START_TAG) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
#ifndef AAPT_IO_DATA_H
|
||||
#define AAPT_IO_DATA_H
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <utils/FileMap.h>
|
||||
#include <memory>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
#include "utils/FileMap.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
@@ -39,20 +40,20 @@ class IData {
|
||||
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) {}
|
||||
: data_(std::move(data)), offset_(offset), len_(len) {}
|
||||
|
||||
const void* data() const override {
|
||||
return static_cast<const uint8_t*>(mData->data()) + mOffset;
|
||||
return static_cast<const uint8_t*>(data_->data()) + offset_;
|
||||
}
|
||||
|
||||
size_t size() const override { return mLen; }
|
||||
size_t size() const override { return len_; }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DataSegment);
|
||||
|
||||
std::unique_ptr<IData> mData;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
std::unique_ptr<IData> data_;
|
||||
size_t offset_;
|
||||
size_t len_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -63,14 +64,14 @@ class DataSegment : public IData {
|
||||
class MmappedData : public IData {
|
||||
public:
|
||||
explicit MmappedData(android::FileMap&& map)
|
||||
: mMap(std::forward<android::FileMap>(map)) {}
|
||||
: map_(std::forward<android::FileMap>(map)) {}
|
||||
|
||||
const void* data() const override { return mMap.getDataPtr(); }
|
||||
const void* data() const override { return map_.getDataPtr(); }
|
||||
|
||||
size_t size() const override { return mMap.getDataLength(); }
|
||||
size_t size() const override { return map_.getDataLength(); }
|
||||
|
||||
private:
|
||||
android::FileMap mMap;
|
||||
android::FileMap map_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -81,15 +82,15 @@ class MmappedData : public IData {
|
||||
class MallocData : public IData {
|
||||
public:
|
||||
MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
|
||||
: mData(std::move(data)), mSize(size) {}
|
||||
: data_(std::move(data)), size_(size) {}
|
||||
|
||||
const void* data() const override { return mData.get(); }
|
||||
const void* data() const override { return data_.get(); }
|
||||
|
||||
size_t size() const override { return mSize; }
|
||||
size_t size() const override { return size_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<const uint8_t[]> mData;
|
||||
size_t mSize;
|
||||
std::unique_ptr<const uint8_t[]> data_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,23 +21,23 @@
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
IFile* IFile::createFileSegment(size_t offset, size_t len) {
|
||||
FileSegment* fileSegment = new FileSegment(this, offset, len);
|
||||
mSegments.push_back(std::unique_ptr<IFile>(fileSegment));
|
||||
return fileSegment;
|
||||
IFile* IFile::CreateFileSegment(size_t offset, size_t len) {
|
||||
FileSegment* file_segment = new FileSegment(this, offset, len);
|
||||
segments_.push_back(std::unique_ptr<IFile>(file_segment));
|
||||
return file_segment;
|
||||
}
|
||||
|
||||
std::unique_ptr<IData> FileSegment::openAsData() {
|
||||
std::unique_ptr<IData> data = mFile->openAsData();
|
||||
if (!data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (mOffset <= data->size() - mLen) {
|
||||
return util::make_unique<DataSegment>(std::move(data), mOffset, mLen);
|
||||
}
|
||||
std::unique_ptr<IData> FileSegment::OpenAsData() {
|
||||
std::unique_ptr<IData> data = file_->OpenAsData();
|
||||
if (!data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (offset_ <= data->size() - len_) {
|
||||
return util::make_unique<DataSegment>(std::move(data), offset_, len_);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
#ifndef AAPT_IO_FILE_H
|
||||
#define AAPT_IO_FILE_H
|
||||
|
||||
#include "Source.h"
|
||||
#include "io/Data.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Source.h"
|
||||
#include "io/Data.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
@@ -49,7 +50,7 @@ class IFile {
|
||||
*
|
||||
* Returns nullptr on failure.
|
||||
*/
|
||||
virtual std::unique_ptr<IData> openAsData() = 0;
|
||||
virtual std::unique_ptr<IData> OpenAsData() = 0;
|
||||
|
||||
/**
|
||||
* Returns the source of this file. This is for presentation to the user and
|
||||
@@ -58,9 +59,9 @@ class IFile {
|
||||
* the files within
|
||||
* a ZIP archive from the path to the containing ZIP archive.
|
||||
*/
|
||||
virtual const Source& getSource() const = 0;
|
||||
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
|
||||
@@ -68,7 +69,7 @@ class IFile {
|
||||
// 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;
|
||||
std::list<std::unique_ptr<IFile>> segments_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,26 +79,26 @@ class IFile {
|
||||
class FileSegment : public IFile {
|
||||
public:
|
||||
explicit FileSegment(IFile* file, size_t offset, size_t len)
|
||||
: mFile(file), mOffset(offset), mLen(len) {}
|
||||
: file_(file), offset_(offset), len_(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 file_->GetSource(); }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FileSegment);
|
||||
|
||||
IFile* mFile;
|
||||
size_t mOffset;
|
||||
size_t mLen;
|
||||
IFile* file_;
|
||||
size_t offset_;
|
||||
size_t len_;
|
||||
};
|
||||
|
||||
class IFileCollectionIterator {
|
||||
public:
|
||||
virtual ~IFileCollectionIterator() = default;
|
||||
|
||||
virtual bool hasNext() = 0;
|
||||
virtual IFile* next() = 0;
|
||||
virtual bool HasNext() = 0;
|
||||
virtual IFile* Next() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -109,8 +110,8 @@ class IFileCollection {
|
||||
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
|
||||
|
||||
@@ -14,65 +14,62 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Source.h"
|
||||
#include "io/FileSystem.h"
|
||||
|
||||
#include "utils/FileMap.h"
|
||||
|
||||
#include "Source.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/Maybe.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <utils/FileMap.h>
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
RegularFile::RegularFile(const Source& source) : mSource(source) {
|
||||
}
|
||||
RegularFile::RegularFile(const Source& source) : source_(source) {}
|
||||
|
||||
std::unique_ptr<IData> RegularFile::openAsData() {
|
||||
android::FileMap map;
|
||||
if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
|
||||
if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
|
||||
return util::make_unique<MmappedData>(std::move(map.value()));
|
||||
}
|
||||
return util::make_unique<EmptyData>();
|
||||
std::unique_ptr<IData> RegularFile::OpenAsData() {
|
||||
android::FileMap map;
|
||||
if (Maybe<android::FileMap> map = file::MmapPath(source_.path, nullptr)) {
|
||||
if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
|
||||
return util::make_unique<MmappedData>(std::move(map.value()));
|
||||
}
|
||||
return {};
|
||||
return util::make_unique<EmptyData>();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const Source& RegularFile::getSource() const {
|
||||
return mSource;
|
||||
const Source& RegularFile::GetSource() const { return source_; }
|
||||
|
||||
FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
|
||||
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
|
||||
|
||||
bool FileCollectionIterator::HasNext() { return current_ != end_; }
|
||||
|
||||
IFile* FileCollectionIterator::Next() {
|
||||
IFile* result = current_->second.get();
|
||||
++current_;
|
||||
return result;
|
||||
}
|
||||
|
||||
FileCollectionIterator::FileCollectionIterator(FileCollection* collection) :
|
||||
mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
|
||||
IFile* FileCollection::InsertFile(const StringPiece& path) {
|
||||
return (files_[path.ToString()] =
|
||||
util::make_unique<RegularFile>(Source(path)))
|
||||
.get();
|
||||
}
|
||||
|
||||
bool FileCollectionIterator::hasNext() {
|
||||
return mCurrent != mEnd;
|
||||
IFile* FileCollection::FindFile(const StringPiece& path) {
|
||||
auto iter = files_.find(path.ToString());
|
||||
if (iter != files_.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IFile* FileCollectionIterator::next() {
|
||||
IFile* result = mCurrent->second.get();
|
||||
++mCurrent;
|
||||
return result;
|
||||
std::unique_ptr<IFileCollectionIterator> FileCollection::Iterator() {
|
||||
return util::make_unique<FileCollectionIterator>(this);
|
||||
}
|
||||
|
||||
IFile* FileCollection::insertFile(const StringPiece& path) {
|
||||
return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get();
|
||||
}
|
||||
|
||||
IFile* FileCollection::findFile(const StringPiece& path) {
|
||||
auto iter = mFiles.find(path.toString());
|
||||
if (iter != mFiles.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() {
|
||||
return util::make_unique<FileCollectionIterator>(this);
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#ifndef AAPT_IO_FILESYSTEM_H
|
||||
#define AAPT_IO_FILESYSTEM_H
|
||||
|
||||
#include "io/File.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "io/File.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
@@ -31,11 +31,11 @@ class RegularFile : public IFile {
|
||||
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;
|
||||
Source source_;
|
||||
};
|
||||
|
||||
class FileCollection;
|
||||
@@ -44,11 +44,11 @@ class FileCollectionIterator : public IFileCollectionIterator {
|
||||
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;
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -59,13 +59,13 @@ class FileCollection : public IFileCollection {
|
||||
/**
|
||||
* 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;
|
||||
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;
|
||||
std::map<std::string, std::unique_ptr<IFile>> files_;
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
|
||||
@@ -22,23 +22,23 @@
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
bool copy(OutputStream* out, InputStream* in) {
|
||||
const void* inBuffer;
|
||||
int inLen;
|
||||
while (in->Next(&inBuffer, &inLen)) {
|
||||
void* outBuffer;
|
||||
int outLen;
|
||||
if (!out->Next(&outBuffer, &outLen)) {
|
||||
return !out->HadError();
|
||||
}
|
||||
|
||||
const int bytesToCopy = std::min(inLen, outLen);
|
||||
memcpy(outBuffer, inBuffer, bytesToCopy);
|
||||
out->BackUp(outLen - bytesToCopy);
|
||||
in->BackUp(inLen - bytesToCopy);
|
||||
bool Copy(OutputStream* out, InputStream* in) {
|
||||
const void* in_buffer;
|
||||
int in_len;
|
||||
while (in->Next(&in_buffer, &in_len)) {
|
||||
void* out_buffer;
|
||||
int out_len;
|
||||
if (!out->Next(&out_buffer, &out_len)) {
|
||||
return !out->HadError();
|
||||
}
|
||||
return !in->HadError();
|
||||
|
||||
const int bytes_to_copy = std::min(in_len, out_len);
|
||||
memcpy(out_buffer, in_buffer, bytes_to_copy);
|
||||
out->BackUp(out_len - bytes_to_copy);
|
||||
in->BackUp(in_len - bytes_to_copy);
|
||||
}
|
||||
return !in->HadError();
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
#ifndef AAPT_IO_IO_H
|
||||
#define AAPT_IO_IO_H
|
||||
|
||||
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
|
||||
#include <string>
|
||||
|
||||
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
@@ -29,7 +30,7 @@ namespace io {
|
||||
*
|
||||
* The code style here matches the protobuf style.
|
||||
*/
|
||||
class InputStream : public google::protobuf::io::ZeroCopyInputStream {
|
||||
class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
|
||||
public:
|
||||
virtual std::string GetError() const { return {}; }
|
||||
|
||||
@@ -42,7 +43,7 @@ class InputStream : public google::protobuf::io::ZeroCopyInputStream {
|
||||
*
|
||||
* The code style here matches the protobuf style.
|
||||
*/
|
||||
class OutputStream : public google::protobuf::io::ZeroCopyOutputStream {
|
||||
class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
|
||||
public:
|
||||
virtual std::string GetError() const { return {}; }
|
||||
|
||||
@@ -54,7 +55,7 @@ class OutputStream : public google::protobuf::io::ZeroCopyOutputStream {
|
||||
* If there was an error, check the individual streams' HadError/GetError
|
||||
* methods.
|
||||
*/
|
||||
bool copy(OutputStream* out, InputStream* in);
|
||||
bool Copy(OutputStream* out, InputStream* in);
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
@@ -14,129 +14,128 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Source.h"
|
||||
#include "io/ZipArchive.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <utils/FileMap.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
#include "utils/FileMap.h"
|
||||
#include "ziparchive/zip_archive.h"
|
||||
|
||||
#include "Source.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
|
||||
mZipHandle(handle), mZipEntry(entry), mSource(source) {
|
||||
}
|
||||
ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry,
|
||||
const Source& source)
|
||||
: zip_handle_(handle), zip_entry_(entry), source_(source) {}
|
||||
|
||||
std::unique_ptr<IData> ZipFile::openAsData() {
|
||||
if (mZipEntry.method == kCompressStored) {
|
||||
int fd = GetFileDescriptor(mZipHandle);
|
||||
std::unique_ptr<IData> ZipFile::OpenAsData() {
|
||||
if (zip_entry_.method == kCompressStored) {
|
||||
int fd = GetFileDescriptor(zip_handle_);
|
||||
|
||||
android::FileMap fileMap;
|
||||
bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
|
||||
mZipEntry.uncompressed_length, true);
|
||||
if (!result) {
|
||||
return {};
|
||||
}
|
||||
return util::make_unique<MmappedData>(std::move(fileMap));
|
||||
|
||||
} else {
|
||||
std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
|
||||
new uint8_t[mZipEntry.uncompressed_length]);
|
||||
int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
|
||||
static_cast<uint32_t>(mZipEntry.uncompressed_length));
|
||||
if (result != 0) {
|
||||
return {};
|
||||
}
|
||||
return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
|
||||
android::FileMap file_map;
|
||||
bool result = file_map.create(nullptr, fd, zip_entry_.offset,
|
||||
zip_entry_.uncompressed_length, true);
|
||||
if (!result) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return util::make_unique<MmappedData>(std::move(file_map));
|
||||
|
||||
const Source& ZipFile::getSource() const {
|
||||
return mSource;
|
||||
}
|
||||
|
||||
ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) :
|
||||
mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) {
|
||||
}
|
||||
|
||||
bool ZipFileCollectionIterator::hasNext() {
|
||||
return mCurrent != mEnd;
|
||||
}
|
||||
|
||||
IFile* ZipFileCollectionIterator::next() {
|
||||
IFile* result = mCurrent->second.get();
|
||||
++mCurrent;
|
||||
return result;
|
||||
}
|
||||
|
||||
ZipFileCollection::ZipFileCollection() : mHandle(nullptr) {
|
||||
}
|
||||
|
||||
std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path,
|
||||
std::string* outError) {
|
||||
constexpr static const int32_t kEmptyArchive = -6;
|
||||
|
||||
std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
|
||||
new ZipFileCollection());
|
||||
|
||||
int32_t result = OpenArchive(path.data(), &collection->mHandle);
|
||||
} else {
|
||||
std::unique_ptr<uint8_t[]> data =
|
||||
std::unique_ptr<uint8_t[]>(new uint8_t[zip_entry_.uncompressed_length]);
|
||||
int32_t result =
|
||||
ExtractToMemory(zip_handle_, &zip_entry_, data.get(),
|
||||
static_cast<uint32_t>(zip_entry_.uncompressed_length));
|
||||
if (result != 0) {
|
||||
// If a zip is empty, result will be an error code. This is fine and we should
|
||||
// return an empty ZipFileCollection.
|
||||
if (result == kEmptyArchive) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (outError) *outError = ErrorCodeString(result);
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
void* cookie = nullptr;
|
||||
result = StartIteration(collection->mHandle, &cookie, nullptr, nullptr);
|
||||
if (result != 0) {
|
||||
if (outError) *outError = ErrorCodeString(result);
|
||||
return {};
|
||||
}
|
||||
|
||||
using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
|
||||
IterationEnder iterationEnder(cookie, EndIteration);
|
||||
|
||||
ZipString zipEntryName;
|
||||
ZipEntry zipData;
|
||||
while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
|
||||
std::string zipEntryPath = std::string(reinterpret_cast<const char*>(zipEntryName.name),
|
||||
zipEntryName.name_length);
|
||||
std::string nestedPath = path.toString() + "@" + zipEntryPath;
|
||||
collection->mFiles[zipEntryPath] = util::make_unique<ZipFile>(collection->mHandle,
|
||||
zipData,
|
||||
Source(nestedPath));
|
||||
}
|
||||
|
||||
if (result != -1) {
|
||||
if (outError) *outError = ErrorCodeString(result);
|
||||
return {};
|
||||
}
|
||||
return collection;
|
||||
return util::make_unique<MallocData>(std::move(data),
|
||||
zip_entry_.uncompressed_length);
|
||||
}
|
||||
}
|
||||
|
||||
IFile* ZipFileCollection::findFile(const StringPiece& path) {
|
||||
auto iter = mFiles.find(path.toString());
|
||||
if (iter != mFiles.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
const Source& ZipFile::GetSource() const { return source_; }
|
||||
|
||||
ZipFileCollectionIterator::ZipFileCollectionIterator(
|
||||
ZipFileCollection* collection)
|
||||
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
|
||||
|
||||
bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
|
||||
|
||||
IFile* ZipFileCollectionIterator::Next() {
|
||||
IFile* result = current_->second.get();
|
||||
++current_;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() {
|
||||
return util::make_unique<ZipFileCollectionIterator>(this);
|
||||
ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
|
||||
|
||||
std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
|
||||
const StringPiece& path, std::string* out_error) {
|
||||
constexpr static const int32_t kEmptyArchive = -6;
|
||||
|
||||
std::unique_ptr<ZipFileCollection> collection =
|
||||
std::unique_ptr<ZipFileCollection>(new ZipFileCollection());
|
||||
|
||||
int32_t result = OpenArchive(path.data(), &collection->handle_);
|
||||
if (result != 0) {
|
||||
// If a zip is empty, result will be an error code. This is fine and we
|
||||
// should
|
||||
// return an empty ZipFileCollection.
|
||||
if (result == kEmptyArchive) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (out_error) *out_error = ErrorCodeString(result);
|
||||
return {};
|
||||
}
|
||||
|
||||
void* cookie = nullptr;
|
||||
result = StartIteration(collection->handle_, &cookie, nullptr, nullptr);
|
||||
if (result != 0) {
|
||||
if (out_error) *out_error = ErrorCodeString(result);
|
||||
return {};
|
||||
}
|
||||
|
||||
using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
|
||||
IterationEnder iteration_ender(cookie, EndIteration);
|
||||
|
||||
ZipString zip_entry_name;
|
||||
ZipEntry zip_data;
|
||||
while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) {
|
||||
std::string zip_entry_path =
|
||||
std::string(reinterpret_cast<const char*>(zip_entry_name.name),
|
||||
zip_entry_name.name_length);
|
||||
std::string nested_path = path.ToString() + "@" + zip_entry_path;
|
||||
collection->files_[zip_entry_path] = util::make_unique<ZipFile>(
|
||||
collection->handle_, zip_data, Source(nested_path));
|
||||
}
|
||||
|
||||
if (result != -1) {
|
||||
if (out_error) *out_error = ErrorCodeString(result);
|
||||
return {};
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
IFile* ZipFileCollection::FindFile(const StringPiece& path) {
|
||||
auto iter = files_.find(path.ToString());
|
||||
if (iter != files_.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<IFileCollectionIterator> ZipFileCollection::Iterator() {
|
||||
return util::make_unique<ZipFileCollectionIterator>(this);
|
||||
}
|
||||
|
||||
ZipFileCollection::~ZipFileCollection() {
|
||||
if (mHandle) {
|
||||
CloseArchive(mHandle);
|
||||
}
|
||||
if (handle_) {
|
||||
CloseArchive(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
} // namespace io
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
#ifndef AAPT_IO_ZIPARCHIVE_H
|
||||
#define AAPT_IO_ZIPARCHIVE_H
|
||||
|
||||
#include "ziparchive/zip_archive.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "io/File.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ziparchive/zip_archive.h>
|
||||
#include <map>
|
||||
|
||||
namespace aapt {
|
||||
namespace io {
|
||||
|
||||
@@ -36,13 +37,13 @@ class ZipFile : public IFile {
|
||||
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;
|
||||
ZipArchiveHandle zip_handle_;
|
||||
ZipEntry zip_entry_;
|
||||
Source source_;
|
||||
};
|
||||
|
||||
class ZipFileCollection;
|
||||
@@ -51,11 +52,11 @@ class ZipFileCollectionIterator : public IFileCollectionIterator {
|
||||
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;
|
||||
std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -63,11 +64,11 @@ class ZipFileCollectionIterator : public IFileCollectionIterator {
|
||||
*/
|
||||
class ZipFileCollection : public IFileCollection {
|
||||
public:
|
||||
static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
|
||||
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;
|
||||
|
||||
@@ -75,8 +76,8 @@ class ZipFileCollection : public IFileCollection {
|
||||
friend class ZipFileCollectionIterator;
|
||||
ZipFileCollection();
|
||||
|
||||
ZipArchiveHandle mHandle;
|
||||
std::map<std::string, std::unique_ptr<IFile>> mFiles;
|
||||
ZipArchiveHandle handle_;
|
||||
std::map<std::string, std::unique_ptr<IFile>> files_;
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
|
||||
@@ -15,69 +15,71 @@
|
||||
*/
|
||||
|
||||
#include "java/AnnotationProcessor.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "util/Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
void AnnotationProcessor::appendCommentLine(std::string& comment) {
|
||||
static const std::string sDeprecated = "@deprecated";
|
||||
static const std::string sSystemApi = "@SystemApi";
|
||||
void AnnotationProcessor::AppendCommentLine(std::string& comment) {
|
||||
static const std::string sDeprecated = "@deprecated";
|
||||
static const std::string sSystemApi = "@SystemApi";
|
||||
|
||||
if (comment.find(sDeprecated) != std::string::npos) {
|
||||
mAnnotationBitMask |= kDeprecated;
|
||||
}
|
||||
if (comment.find(sDeprecated) != std::string::npos) {
|
||||
annotation_bit_mask_ |= kDeprecated;
|
||||
}
|
||||
|
||||
std::string::size_type idx = comment.find(sSystemApi);
|
||||
if (idx != std::string::npos) {
|
||||
mAnnotationBitMask |= kSystemApi;
|
||||
comment.erase(comment.begin() + idx, comment.begin() + idx + sSystemApi.size());
|
||||
}
|
||||
std::string::size_type idx = comment.find(sSystemApi);
|
||||
if (idx != std::string::npos) {
|
||||
annotation_bit_mask_ |= kSystemApi;
|
||||
comment.erase(comment.begin() + idx,
|
||||
comment.begin() + idx + sSystemApi.size());
|
||||
}
|
||||
|
||||
if (util::trimWhitespace(comment).empty()) {
|
||||
return;
|
||||
}
|
||||
if (util::TrimWhitespace(comment).empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mHasComments) {
|
||||
mHasComments = true;
|
||||
mComment << "/**";
|
||||
}
|
||||
if (!has_comments_) {
|
||||
has_comments_ = true;
|
||||
comment_ << "/**";
|
||||
}
|
||||
|
||||
mComment << "\n * " << std::move(comment);
|
||||
comment_ << "\n * " << std::move(comment);
|
||||
}
|
||||
|
||||
void AnnotationProcessor::appendComment(const StringPiece& comment) {
|
||||
// We need to process line by line to clean-up whitespace and append prefixes.
|
||||
for (StringPiece line : util::tokenize(comment, '\n')) {
|
||||
line = util::trimWhitespace(line);
|
||||
if (!line.empty()) {
|
||||
std::string lineCopy = line.toString();
|
||||
appendCommentLine(lineCopy);
|
||||
}
|
||||
void AnnotationProcessor::AppendComment(const StringPiece& comment) {
|
||||
// We need to process line by line to clean-up whitespace and append prefixes.
|
||||
for (StringPiece line : util::Tokenize(comment, '\n')) {
|
||||
line = util::TrimWhitespace(line);
|
||||
if (!line.empty()) {
|
||||
std::string lineCopy = line.ToString();
|
||||
AppendCommentLine(lineCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnnotationProcessor::appendNewLine() {
|
||||
mComment << "\n *";
|
||||
void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; }
|
||||
|
||||
void AnnotationProcessor::WriteToStream(std::ostream* out,
|
||||
const StringPiece& prefix) const {
|
||||
if (has_comments_) {
|
||||
std::string result = comment_.str();
|
||||
for (StringPiece line : util::Tokenize(result, '\n')) {
|
||||
*out << prefix << line << "\n";
|
||||
}
|
||||
*out << prefix << " */"
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (annotation_bit_mask_ & kDeprecated) {
|
||||
*out << prefix << "@Deprecated\n";
|
||||
}
|
||||
|
||||
if (annotation_bit_mask_ & kSystemApi) {
|
||||
*out << prefix << "@android.annotation.SystemApi\n";
|
||||
}
|
||||
}
|
||||
|
||||
void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) const {
|
||||
if (mHasComments) {
|
||||
std::string result = mComment.str();
|
||||
for (StringPiece line : util::tokenize(result, '\n')) {
|
||||
*out << prefix << line << "\n";
|
||||
}
|
||||
*out << prefix << " */" << "\n";
|
||||
}
|
||||
|
||||
if (mAnnotationBitMask & kDeprecated) {
|
||||
*out << prefix << "@Deprecated\n";
|
||||
}
|
||||
|
||||
if (mAnnotationBitMask & kSystemApi) {
|
||||
*out << prefix << "@android.annotation.SystemApi\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
|
||||
#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
@@ -58,15 +58,15 @@ class AnnotationProcessor {
|
||||
* configurations,
|
||||
* we need to collect all the comments.
|
||||
*/
|
||||
void appendComment(const StringPiece& comment);
|
||||
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;
|
||||
void WriteToStream(std::ostream* out, const StringPiece& prefix) const;
|
||||
|
||||
private:
|
||||
enum : uint32_t {
|
||||
@@ -74,12 +74,12 @@ class AnnotationProcessor {
|
||||
kSystemApi = 0x02,
|
||||
};
|
||||
|
||||
std::stringstream mComment;
|
||||
std::stringstream comment_;
|
||||
std::stringstream mAnnotations;
|
||||
bool mHasComments = false;
|
||||
uint32_t mAnnotationBitMask = 0;
|
||||
bool has_comments_ = false;
|
||||
uint32_t annotation_bit_mask_ = 0;
|
||||
|
||||
void appendCommentLine(std::string& line);
|
||||
void AppendCommentLine(std::string& line);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,38 +15,39 @@
|
||||
*/
|
||||
|
||||
#include "java/AnnotationProcessor.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(AnnotationProcessorTest, EmitsDeprecated) {
|
||||
const char* comment = "Some comment, and it should contain a marker word, "
|
||||
"something that marks this resource as nor needed. "
|
||||
"{@deprecated That's the marker! }";
|
||||
const char* comment =
|
||||
"Some comment, and it should contain a marker word, "
|
||||
"something that marks this resource as nor needed. "
|
||||
"{@deprecated That's the marker! }";
|
||||
|
||||
AnnotationProcessor processor;
|
||||
processor.appendComment(comment);
|
||||
AnnotationProcessor processor;
|
||||
processor.AppendComment(comment);
|
||||
|
||||
std::stringstream result;
|
||||
processor.writeToStream(&result, "");
|
||||
std::string annotations = result.str();
|
||||
std::stringstream result;
|
||||
processor.WriteToStream(&result, "");
|
||||
std::string annotations = result.str();
|
||||
|
||||
EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
|
||||
EXPECT_NE(std::string::npos, annotations.find("@Deprecated"));
|
||||
}
|
||||
|
||||
TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
|
||||
AnnotationProcessor processor;
|
||||
processor.appendComment("@SystemApi This is a system API");
|
||||
AnnotationProcessor processor;
|
||||
processor.AppendComment("@SystemApi This is a system API");
|
||||
|
||||
std::stringstream result;
|
||||
processor.writeToStream(&result, "");
|
||||
std::string annotations = result.str();
|
||||
std::stringstream result;
|
||||
processor.WriteToStream(&result, "");
|
||||
std::string annotations = result.str();
|
||||
|
||||
EXPECT_NE(std::string::npos, annotations.find("@android.annotation.SystemApi"));
|
||||
EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
|
||||
EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
|
||||
EXPECT_NE(std::string::npos,
|
||||
annotations.find("@android.annotation.SystemApi"));
|
||||
EXPECT_EQ(std::string::npos, annotations.find("@SystemApi"));
|
||||
EXPECT_NE(std::string::npos, annotations.find("This is a system API"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -15,61 +15,59 @@
|
||||
*/
|
||||
|
||||
#include "java/ClassDefinition.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <ostream>
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool ClassDefinition::empty() const {
|
||||
for (const std::unique_ptr<ClassMember>& member : mMembers) {
|
||||
if (!member->empty()) {
|
||||
return false;
|
||||
}
|
||||
for (const std::unique_ptr<ClassMember>& member : members_) {
|
||||
if (!member->empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClassDefinition::writeToStream(const StringPiece& prefix, bool final,
|
||||
void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const {
|
||||
if (mMembers.empty() && !mCreateIfEmpty) {
|
||||
return;
|
||||
}
|
||||
if (members_.empty() && !create_if_empty_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
ClassMember::WriteToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public ";
|
||||
if (mQualifier == ClassQualifier::Static) {
|
||||
*out << "static ";
|
||||
}
|
||||
*out << "final class " << mName << " {\n";
|
||||
*out << prefix << "public ";
|
||||
if (qualifier_ == ClassQualifier::Static) {
|
||||
*out << "static ";
|
||||
}
|
||||
*out << "final class " << name_ << " {\n";
|
||||
|
||||
std::string newPrefix = prefix.toString();
|
||||
newPrefix.append(kIndent);
|
||||
std::string new_prefix = prefix.ToString();
|
||||
new_prefix.append(kIndent);
|
||||
|
||||
for (const std::unique_ptr<ClassMember>& member : mMembers) {
|
||||
member->writeToStream(newPrefix, final, out);
|
||||
*out << "\n";
|
||||
}
|
||||
for (const std::unique_ptr<ClassMember>& member : members_) {
|
||||
member->WriteToStream(new_prefix, final, out);
|
||||
*out << "\n";
|
||||
}
|
||||
|
||||
*out << prefix << "}";
|
||||
*out << prefix << "}";
|
||||
}
|
||||
|
||||
constexpr static const char* sWarningHeader =
|
||||
"/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
|
||||
" *\n"
|
||||
" * This class was automatically generated by the\n"
|
||||
" * aapt tool from the resource data it found. It\n"
|
||||
" * should not be modified by hand.\n"
|
||||
" */\n\n";
|
||||
"/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
|
||||
" *\n"
|
||||
" * This class was automatically generated by the\n"
|
||||
" * aapt tool from the resource data it found. It\n"
|
||||
" * should not be modified by hand.\n"
|
||||
" */\n\n";
|
||||
|
||||
bool ClassDefinition::writeJavaFile(const ClassDefinition* def,
|
||||
const StringPiece& package,
|
||||
bool final,
|
||||
bool ClassDefinition::WriteJavaFile(const ClassDefinition* def,
|
||||
const StringPiece& package, bool final,
|
||||
std::ostream* out) {
|
||||
*out << sWarningHeader << "package " << package << ";\n\n";
|
||||
def->writeToStream("", final, out);
|
||||
return bool(*out);
|
||||
*out << sWarningHeader << "package " << package << ";\n\n";
|
||||
def->WriteToStream("", final, out);
|
||||
return bool(*out);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
} // namespace aapt
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
#ifndef AAPT_JAVA_CLASSDEFINITION_H
|
||||
#define AAPT_JAVA_CLASSDEFINITION_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "android-base/macros.h"
|
||||
|
||||
#include "Resource.h"
|
||||
#include "java/AnnotationProcessor.h"
|
||||
#include "util/StringPiece.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// The number of attributes to emit per line in a Styleable array.
|
||||
@@ -36,38 +37,38 @@ class ClassMember {
|
||||
public:
|
||||
virtual ~ClassMember() = default;
|
||||
|
||||
AnnotationProcessor* getCommentBuilder() { return &mProcessor; }
|
||||
AnnotationProcessor* GetCommentBuilder() { return &processor_; }
|
||||
|
||||
virtual bool empty() const = 0;
|
||||
|
||||
virtual void writeToStream(const StringPiece& prefix, bool final,
|
||||
virtual void WriteToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const {
|
||||
mProcessor.writeToStream(out, prefix);
|
||||
processor_.WriteToStream(out, prefix);
|
||||
}
|
||||
|
||||
private:
|
||||
AnnotationProcessor mProcessor;
|
||||
AnnotationProcessor processor_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class PrimitiveMember : public ClassMember {
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const T& val)
|
||||
: mName(name.toString()), mVal(val) {}
|
||||
: name_(name.ToString()), val_(val) {}
|
||||
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
void WriteToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
ClassMember::WriteToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static " << (final ? "final " : "") << "int "
|
||||
<< mName << "=" << mVal << ";";
|
||||
<< name_ << "=" << val_ << ";";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
T mVal;
|
||||
std::string name_;
|
||||
T val_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
};
|
||||
@@ -79,21 +80,21 @@ template <>
|
||||
class PrimitiveMember<std::string> : public ClassMember {
|
||||
public:
|
||||
PrimitiveMember(const StringPiece& name, const std::string& val)
|
||||
: mName(name.toString()), mVal(val) {}
|
||||
: name_(name.ToString()), val_(val) {}
|
||||
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
void WriteToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
ClassMember::WriteToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static " << (final ? "final " : "") << "String "
|
||||
<< mName << "=\"" << mVal << "\";";
|
||||
<< name_ << "=\"" << val_ << "\";";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mVal;
|
||||
std::string name_;
|
||||
std::string val_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
|
||||
};
|
||||
@@ -106,20 +107,20 @@ template <typename T>
|
||||
class PrimitiveArrayMember : public ClassMember {
|
||||
public:
|
||||
explicit PrimitiveArrayMember(const StringPiece& name)
|
||||
: mName(name.toString()) {}
|
||||
: name_(name.ToString()) {}
|
||||
|
||||
void addElement(const T& val) { mElements.push_back(val); }
|
||||
void AddElement(const T& val) { elements_.push_back(val); }
|
||||
|
||||
bool empty() const override { return false; }
|
||||
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
void WriteToStream(const StringPiece& prefix, bool final,
|
||||
std::ostream* out) const override {
|
||||
ClassMember::writeToStream(prefix, final, out);
|
||||
ClassMember::WriteToStream(prefix, final, out);
|
||||
|
||||
*out << prefix << "public static final int[] " << mName << "={";
|
||||
*out << prefix << "public static final int[] " << name_ << "={";
|
||||
|
||||
const auto begin = mElements.begin();
|
||||
const auto end = mElements.end();
|
||||
const auto begin = elements_.begin();
|
||||
const auto end = elements_.end();
|
||||
for (auto current = begin; current != end; ++current) {
|
||||
if (std::distance(begin, current) % kAttribsPerLine == 0) {
|
||||
*out << "\n" << prefix << kIndent << kIndent;
|
||||
@@ -134,8 +135,8 @@ class PrimitiveArrayMember : public ClassMember {
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::vector<T> mElements;
|
||||
std::string name_;
|
||||
std::vector<T> elements_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
|
||||
};
|
||||
@@ -146,29 +147,29 @@ enum class ClassQualifier { None, Static };
|
||||
|
||||
class ClassDefinition : public ClassMember {
|
||||
public:
|
||||
static bool writeJavaFile(const ClassDefinition* def,
|
||||
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) {}
|
||||
: name_(name.ToString()),
|
||||
qualifier_(qualifier),
|
||||
create_if_empty_(createIfEmpty) {}
|
||||
|
||||
void addMember(std::unique_ptr<ClassMember> member) {
|
||||
mMembers.push_back(std::move(member));
|
||||
void AddMember(std::unique_ptr<ClassMember> member) {
|
||||
members_.push_back(std::move(member));
|
||||
}
|
||||
|
||||
bool empty() const override;
|
||||
void writeToStream(const StringPiece& prefix, bool final,
|
||||
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;
|
||||
std::string name_;
|
||||
ClassQualifier qualifier_;
|
||||
bool create_if_empty_;
|
||||
std::vector<std::unique_ptr<ClassMember>> members_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user