Merge "AAPT2: Better debugging output"

This commit is contained in:
TreeHugger Robot
2017-11-08 00:20:40 +00:00
committed by Android (Google) Code Review
25 changed files with 793 additions and 201 deletions

View File

@@ -113,6 +113,7 @@ cc_library_host_static {
"optimize/VersionCollapser.cpp",
"process/SymbolTable.cpp",
"split/TableSplitter.cpp",
"text/Printer.cpp",
"text/Unicode.cpp",
"text/Utf8Iterator.cpp",
"util/BigBuffer.cpp",

View File

@@ -882,6 +882,11 @@ std::string ConfigDescription::GetBcp47LanguageTag(bool canonicalize) const {
return std::string(locale);
}
std::string ConfigDescription::to_string() const {
const android::String8 str = toString();
return std::string(str.string(), str.size());
}
bool ConfigDescription::Dominates(const ConfigDescription& o) const {
if (*this == o) {
return true;

View File

@@ -64,6 +64,8 @@ struct ConfigDescription : public android::ResTable_config {
// Returns the BCP-47 language tag of this configuration's locale.
std::string GetBcp47LanguageTag(bool canonicalize = false) const;
std::string to_string() const;
/**
* A configuration X dominates another configuration Y, if X has at least the
* precedence of Y and X is strictly more general than Y: for any type defined

View File

@@ -17,7 +17,6 @@
#include "Debug.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <memory>
#include <queue>
@@ -25,120 +24,244 @@
#include <vector>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "text/Printer.h"
#include "util/Util.h"
using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
namespace {
class PrintVisitor : public ConstValueVisitor {
class ValueHeadlinePrinter : public ConstValueVisitor {
public:
using ConstValueVisitor::Visit;
explicit ValueHeadlinePrinter(const std::string& package, Printer* printer)
: package_(package), printer_(printer) {
}
void Visit(const Attribute* attr) override {
std::cout << "(attr) type=";
attr->PrintMask(&std::cout);
static constexpr uint32_t kMask =
android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
printer_->Print("(attr) type=");
printer_->Print(attr->MaskString());
if (!attr->symbols.empty()) {
printer_->Print(StringPrintf(" size=%zd", attr->symbols.size()));
}
}
void Visit(const Style* style) override {
printer_->Print(StringPrintf("(style) size=%zd", style->entries.size()));
if (style->parent) {
printer_->Print(" parent=");
const Reference& parent_ref = style->parent.value();
if (parent_ref.name) {
if (parent_ref.private_reference) {
printer_->Print("*");
}
const ResourceName& parent_name = parent_ref.name.value();
if (package_ != parent_name.package) {
printer_->Print(parent_name.package);
printer_->Print(":");
}
printer_->Print(to_string(parent_name.type));
printer_->Print("/");
printer_->Print(parent_name.entry);
if (parent_ref.id) {
printer_->Print(" (");
printer_->Print(parent_ref.id.value().to_string());
printer_->Print(")");
}
} else if (parent_ref.id) {
printer_->Print(parent_ref.id.value().to_string());
} else {
printer_->Print("???");
}
}
}
void Visit(const Array* array) override {
printer_->Print(StringPrintf("(array) size=%zd", array->elements.size()));
}
void Visit(const Plural* plural) override {
size_t count = std::count_if(plural->values.begin(), plural->values.end(),
[](const std::unique_ptr<Item>& v) { return v != nullptr; });
printer_->Print(StringPrintf("(plurals) size=%zd", count));
}
void Visit(const Styleable* styleable) override {
printer_->Println(StringPrintf("(styleable) size=%zd", styleable->entries.size()));
}
void VisitItem(const Item* item) override {
// Pretty much guaranteed to be one line.
if (const Reference* ref = ValueCast<Reference>(item)) {
// Special case Reference so that we can print local resources without a package name.
ref->PrettyPrint(package_, printer_);
} else {
item->PrettyPrint(printer_);
}
}
private:
std::string package_;
Printer* printer_;
};
class ValueBodyPrinter : public ConstValueVisitor {
public:
using ConstValueVisitor::Visit;
explicit ValueBodyPrinter(const std::string& package, Printer* printer)
: package_(package), printer_(printer) {
}
void Visit(const Attribute* attr) override {
constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS;
if (attr->type_mask & kMask) {
for (const auto& symbol : attr->symbols) {
std::cout << "\n " << symbol.symbol.name.value().entry;
printer_->Print(symbol.symbol.name.value().entry);
if (symbol.symbol.id) {
std::cout << " (" << symbol.symbol.id.value() << ")";
printer_->Print("(");
printer_->Print(symbol.symbol.id.value().to_string());
printer_->Print(")");
}
std::cout << " = " << symbol.value;
printer_->Println(StringPrintf("=0x%08x", symbol.value));
}
}
}
void Visit(const Style* style) override {
std::cout << "(style)";
if (style->parent) {
const Reference& parent_ref = style->parent.value();
std::cout << " parent=";
if (parent_ref.name) {
if (parent_ref.private_reference) {
std::cout << "*";
}
std::cout << parent_ref.name.value() << " ";
}
if (parent_ref.id) {
std::cout << parent_ref.id.value();
}
}
for (const auto& entry : style->entries) {
std::cout << "\n ";
if (entry.key.name) {
const ResourceName& name = entry.key.name.value();
if (!name.package.empty()) {
std::cout << name.package << ":";
if (!name.package.empty() && name.package != package_) {
printer_->Print(name.package);
printer_->Print(":");
}
std::cout << name.entry;
printer_->Print(name.entry);
if (entry.key.id) {
printer_->Print("(");
printer_->Print(entry.key.id.value().to_string());
printer_->Print(")");
}
} else if (entry.key.id) {
printer_->Print(entry.key.id.value().to_string());
} else {
printer_->Print("???");
}
if (entry.key.id) {
std::cout << "(" << entry.key.id.value() << ")";
}
std::cout << "=" << *entry.value;
printer_->Print("=");
PrintItem(*entry.value);
printer_->Println();
}
}
void Visit(const Array* array) override {
array->Print(&std::cout);
const size_t count = array->elements.size();
printer_->Print("[");
if (count > 0) {
for (size_t i = 0u; i < count; i++) {
if (i != 0u && i % 4u == 0u) {
printer_->Println();
printer_->Print(" ");
}
PrintItem(*array->elements[i]);
if (i != count - 1) {
printer_->Print(", ");
}
}
printer_->Println("]");
}
}
void Visit(const Plural* plural) override {
plural->Print(&std::cout);
constexpr std::array<const char*, Plural::Count> kPluralNames = {
{"zero", "one", "two", "few", "many", "other"}};
for (size_t i = 0; i < Plural::Count; i++) {
if (plural->values[i] != nullptr) {
printer_->Print(StringPrintf("%s=", kPluralNames[i]));
PrintItem(*plural->values[i]);
printer_->Println();
}
}
}
void Visit(const Styleable* styleable) override {
std::cout << "(styleable)";
for (const auto& attr : styleable->entries) {
std::cout << "\n ";
if (attr.name) {
const ResourceName& name = attr.name.value();
if (!name.package.empty()) {
std::cout << name.package << ":";
if (!name.package.empty() && name.package != package_) {
printer_->Print(name.package);
printer_->Print(":");
}
printer_->Print(name.entry);
if (attr.id) {
printer_->Print("(");
printer_->Print(attr.id.value().to_string());
printer_->Print(")");
}
std::cout << name.entry;
}
if (attr.id) {
std::cout << "(" << attr.id.value() << ")";
printer_->Print(attr.id.value().to_string());
}
}
}
void VisitItem(const Item* item) override {
item->Print(&std::cout);
// Intentionally left empty, we already printed the Items.
}
private:
void PrintItem(const Item& item) {
if (const Reference* ref = ValueCast<Reference>(&item)) {
// Special case Reference so that we can print local resources without a package name.
ref->PrettyPrint(package_, printer_);
} else {
item.PrettyPrint(printer_);
}
}
std::string package_;
Printer* printer_;
};
} // namespace
void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) {
PrintVisitor visitor;
void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
Printer* printer) {
for (const auto& package : table.packages) {
std::cout << "Package name=" << package->name;
if (package->id) {
std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
}
std::cout << std::endl;
ValueHeadlinePrinter headline_printer(package->name, printer);
ValueBodyPrinter body_printer(package->name, printer);
printer->Print("Package name=");
printer->Print(package->name);
if (package->id) {
printer->Print(StringPrintf(" id=%02x", package->id.value()));
}
printer->Println();
printer->Indent();
for (const auto& type : package->types) {
std::cout << "\n type " << type->type;
printer->Print("type ");
printer->Print(to_string(type->type));
if (type->id) {
std::cout << " id=" << std::hex << (int)type->id.value() << std::dec;
printer->Print(StringPrintf(" id=%02x", type->id.value()));
}
std::cout << " entryCount=" << type->entries.size() << std::endl;
printer->Println(StringPrintf(" entryCount=%zd", type->entries.size()));
std::vector<const ResourceEntry*> sorted_entries;
for (const auto& entry : type->entries) {
@@ -156,35 +279,54 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
sorted_entries.insert(iter, entry.get());
}
printer->Indent();
for (const ResourceEntry* entry : sorted_entries) {
const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0),
entry->id.value_or_default(0));
const ResourceName name(package->name, type->type, entry->name);
std::cout << " spec resource " << id << " " << name;
printer->Print("resource ");
printer->Print(id.to_string());
printer->Print(" ");
// Write the name without the package (this is obvious and too verbose).
printer->Print(to_string(type->type));
printer->Print("/");
printer->Print(entry->name);
switch (entry->symbol_status.state) {
case SymbolState::kPublic:
std::cout << " PUBLIC";
printer->Print(" PUBLIC");
break;
case SymbolState::kPrivate:
std::cout << " _PRIVATE_";
printer->Print(" _PRIVATE_");
break;
default:
case SymbolState::kUndefined:
// Print nothing.
break;
}
std::cout << std::endl;
printer->Println();
printer->Indent();
for (const auto& value : entry->values) {
std::cout << " (" << value->config << ") ";
value->value->Accept(&visitor);
printer->Print("(");
printer->Print(value->config.to_string());
printer->Print(") ");
value->value->Accept(&headline_printer);
if (options.show_sources && !value->value->GetSource().path.empty()) {
std::cout << " src=" << value->value->GetSource();
printer->Print(" src=");
printer->Print(value->value->GetSource().to_string());
}
std::cout << std::endl;
printer->Println();
printer->Indent();
value->value->Accept(&body_printer);
printer->Undent();
}
printer->Undent();
}
printer->Undent();
}
printer->Undent();
}
}

View File

@@ -22,6 +22,7 @@
#include "Resource.h"
#include "ResourceTable.h"
#include "text/Printer.h"
#include "xml/XmlDom.h"
namespace aapt {
@@ -31,9 +32,9 @@ struct DebugPrintTableOptions {
};
struct Debug {
static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {});
static void PrintStyleGraph(ResourceTable* table,
const ResourceName& target_style);
static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
text::Printer* printer);
static void PrintStyleGraph(ResourceTable* table, const ResourceName& target_style);
static void DumpHex(const void* data, size_t len);
static void DumpXml(const xml::XmlResource& doc);
};

View File

@@ -17,13 +17,34 @@
#include "Resource.h"
#include <map>
#include <sstream>
#include <string>
using android::StringPiece;
#include "android-base/stringprintf.h"
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
StringPiece ToString(ResourceType type) {
std::string ResourceId::to_string() const {
return StringPrintf("0x%08x", id);
}
std::string ResourceName::to_string() const {
return ResourceNameRef(*this).to_string();
}
std::string ResourceNameRef::to_string() const {
std::ostringstream str_stream;
if (!package.empty()) {
str_stream << package << ":";
}
str_stream << type << "/" << entry;
return str_stream.str();
}
StringPiece to_string(ResourceType type) {
switch (type) {
case ResourceType::kAnim:
return "anim";

View File

@@ -69,11 +69,10 @@ enum class ResourceType {
kXml,
};
android::StringPiece ToString(ResourceType type);
android::StringPiece to_string(ResourceType type);
/**
* Returns a pointer to a valid ResourceType, or nullptr if
* the string was invalid.
* Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
*/
const ResourceType* ParseResourceType(const android::StringPiece& str);
@@ -92,7 +91,7 @@ struct ResourceName {
int compare(const ResourceName& other) const;
bool is_valid() const;
std::string ToString() const;
std::string to_string() const;
};
/**
@@ -115,8 +114,10 @@ struct ResourceNameRef {
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
ResourceNameRef& operator=(const ResourceName& rhs);
ResourceName ToResourceName() const;
bool is_valid() const;
ResourceName ToResourceName() const;
std::string to_string() const;
};
constexpr const uint8_t kAppPackageId = 0x7fu;
@@ -149,6 +150,8 @@ struct ResourceId {
uint8_t package_id() const;
uint8_t type_id() const;
uint16_t entry_id() const;
std::string to_string() const;
};
struct SourcedResourceName {
@@ -229,7 +232,9 @@ inline bool ResourceId::is_valid() const {
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
inline bool ResourceId::is_valid_dynamic() const { return (id & 0x00ff0000u) != 0; }
inline bool ResourceId::is_valid_dynamic() const {
return (id & 0x00ff0000u) != 0;
}
inline uint8_t ResourceId::package_id() const {
return static_cast<uint8_t>(id >> 24);
@@ -259,24 +264,16 @@ inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) {
return lhs.id != rhs.id;
}
inline ::std::ostream& operator<<(::std::ostream& out,
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
<< res_id.id;
out.flags(old_flags);
out.fill(old_fill);
return out;
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& res_id) {
return out << res_id.to_string();
}
//
// ResourceType implementation.
//
inline ::std::ostream& operator<<(::std::ostream& out,
const ResourceType& val) {
return out << ToString(val);
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
return out << to_string(val);
}
//
@@ -315,18 +312,8 @@ inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) {
std::tie(rhs.package, rhs.type, rhs.entry);
}
inline ::std::ostream& operator<<(::std::ostream& out,
const ResourceName& name) {
if (!name.package.empty()) {
out << name.package << ":";
}
return out << name.type << "/" << name.entry;
}
inline std::string ResourceName::ToString() const {
std::stringstream stream;
stream << *this;
return stream.str();
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
return out << name.to_string();
}
//
@@ -370,12 +357,8 @@ inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) {
std::tie(rhs.package, rhs.type, rhs.entry);
}
inline ::std::ostream& operator<<(::std::ostream& out,
const ResourceNameRef& name) {
if (!name.package.empty()) {
out << name.package << ":";
}
return out << name.type << "/" << name.entry;
inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
return out << name.to_string();
}
inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) {
@@ -386,8 +369,7 @@ inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) {
return ResourceNameRef(lhs) != rhs;
}
inline bool operator==(const SourcedResourceName& lhs,
const SourcedResourceName& rhs) {
inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) {
return lhs.name == rhs.name && lhs.line == rhs.line;
}

View File

@@ -17,9 +17,12 @@
#include "ResourceValues.h"
#include <algorithm>
#include <cinttypes>
#include <limits>
#include <set>
#include <sstream>
#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "Resource.h"
@@ -27,8 +30,18 @@
#include "ValueVisitor.h"
#include "util/Util.h"
using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
void Value::PrettyPrint(Printer* printer) const {
std::ostringstream str_stream;
Print(&str_stream);
printer->Print(str_stream.str());
}
std::ostream& operator<<(std::ostream& out, const Value& value) {
value.Print(&out);
return out;
@@ -155,6 +168,49 @@ void Reference::Print(std::ostream* out) const {
}
}
static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, Printer* printer) {
switch (ref.reference_type) {
case Reference::Type::kResource:
printer->Print("@");
break;
case Reference::Type::kAttribute:
printer->Print("?");
break;
}
if (!ref.name && !ref.id) {
printer->Print("null");
return;
}
if (ref.private_reference) {
printer->Print("*");
}
if (ref.name) {
const ResourceName& name = ref.name.value();
if (print_package) {
printer->Print(name.to_string());
} else {
printer->Print(to_string(name.type));
printer->Print("/");
printer->Print(name.entry);
}
} else if (ref.id && ref.id.value().is_valid_dynamic()) {
printer->Print(ref.id.value().to_string());
}
}
void Reference::PrettyPrint(Printer* printer) const {
PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
}
void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
const bool print_package = name ? package != name.value().package : true;
PrettyPrintReferenceImpl(*this, print_package, printer);
}
bool Id::Equals(const Value* value) const {
return ValueCast<Id>(value) != nullptr;
}
@@ -165,11 +221,16 @@ bool Id::Flatten(android::Res_value* out) const {
return true;
}
Id* Id::Clone(StringPool* /*new_pool*/) 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) {}
String::String(const StringPool::Ref& ref) : value(ref) {
}
bool String::Equals(const Value* value) const {
const String* other = ValueCast<String>(value);
@@ -218,7 +279,14 @@ void String::Print(std::ostream* out) const {
*out << "(string) \"" << *value << "\"";
}
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
void String::PrettyPrint(Printer* printer) const {
printer->Print("\"");
printer->Print(*value);
printer->Print("\"");
}
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
}
bool StyledString::Equals(const Value* value) const {
const StyledString* other = ValueCast<StyledString>(value);
@@ -269,7 +337,8 @@ void StyledString::Print(std::ostream* out) const {
}
}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
}
bool FileReference::Equals(const Value* value) const {
const FileReference* other = ValueCast<FileReference>(value);
@@ -302,7 +371,8 @@ void FileReference::Print(std::ostream* out) const {
*out << "(file) " << *path;
}
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {}
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
}
BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
value.dataType = dataType;
@@ -318,7 +388,7 @@ bool BinaryPrimitive::Equals(const Value* value) const {
this->value.data == other->value.data;
}
bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const {
out_value->dataType = value.dataType;
out_value->data = util::HostToDevice32(value.data);
return true;
@@ -329,32 +399,110 @@ BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
}
void BinaryPrimitive::Print(std::ostream* out) const {
*out << StringPrintf("(primitive) type=0x%02x data=0x%08x", value.dataType, value.data);
}
static std::string ComplexToString(uint32_t complex_value, bool fraction) {
using ::android::Res_value;
constexpr std::array<int, 4> kRadixShifts = {{23, 16, 8, 0}};
// Determine the radix that was used.
const uint32_t radix =
(complex_value >> Res_value::COMPLEX_RADIX_SHIFT) & Res_value::COMPLEX_RADIX_MASK;
const uint64_t mantissa = uint64_t{(complex_value >> Res_value::COMPLEX_MANTISSA_SHIFT) &
Res_value::COMPLEX_MANTISSA_MASK}
<< kRadixShifts[radix];
const float value = mantissa * (1.0f / (1 << 23));
std::string str = StringPrintf("%f", value);
const int unit_type =
(complex_value >> Res_value::COMPLEX_UNIT_SHIFT) & Res_value::COMPLEX_UNIT_MASK;
if (fraction) {
switch (unit_type) {
case Res_value::COMPLEX_UNIT_FRACTION:
str += "%";
break;
case Res_value::COMPLEX_UNIT_FRACTION_PARENT:
str += "%p";
break;
default:
str += "???";
break;
}
} else {
switch (unit_type) {
case Res_value::COMPLEX_UNIT_PX:
str += "px";
break;
case Res_value::COMPLEX_UNIT_DIP:
str += "dp";
break;
case Res_value::COMPLEX_UNIT_SP:
str += "sp";
break;
case Res_value::COMPLEX_UNIT_PT:
str += "pt";
break;
case Res_value::COMPLEX_UNIT_IN:
str += "in";
break;
case Res_value::COMPLEX_UNIT_MM:
str += "mm";
break;
default:
str += "???";
break;
}
}
return str;
}
void BinaryPrimitive::PrettyPrint(Printer* printer) const {
using ::android::Res_value;
switch (value.dataType) {
case android::Res_value::TYPE_NULL:
if (value.data == android::Res_value::DATA_NULL_EMPTY) {
*out << "(empty)";
case Res_value::TYPE_NULL:
if (value.data == Res_value::DATA_NULL_EMPTY) {
printer->Print("@empty");
} else {
*out << "(null)";
printer->Print("@null");
}
break;
case android::Res_value::TYPE_INT_DEC:
*out << "(integer) " << static_cast<int32_t>(value.data);
case Res_value::TYPE_INT_DEC:
printer->Print(StringPrintf("%" PRIi32, static_cast<int32_t>(value.data)));
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) 0x" << std::hex << value.data << std::dec;
case Res_value::TYPE_INT_HEX:
printer->Print(StringPrintf("0x%08x", value.data));
break;
case android::Res_value::TYPE_INT_BOOLEAN:
*out << "(boolean) " << (value.data != 0 ? "true" : "false");
case Res_value::TYPE_INT_BOOLEAN:
printer->Print(value.data != 0 ? "true" : "false");
break;
case android::Res_value::TYPE_INT_COLOR_ARGB8:
case android::Res_value::TYPE_INT_COLOR_RGB8:
case android::Res_value::TYPE_INT_COLOR_ARGB4:
case android::Res_value::TYPE_INT_COLOR_RGB4:
*out << "(color) #" << std::hex << value.data << std::dec;
case Res_value::TYPE_INT_COLOR_ARGB8:
case Res_value::TYPE_INT_COLOR_RGB8:
case Res_value::TYPE_INT_COLOR_ARGB4:
case Res_value::TYPE_INT_COLOR_RGB4:
printer->Print(StringPrintf("#%08x", value.data));
break;
case Res_value::TYPE_FLOAT:
printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data)));
break;
case Res_value::TYPE_DIMENSION:
printer->Print(ComplexToString(value.data, false /*fraction*/));
break;
case Res_value::TYPE_FRACTION:
printer->Print(ComplexToString(value.data, true /*fraction*/));
break;
default:
*out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x"
<< std::hex << value.data << std::dec;
printer->Print(StringPrintf("(unknown 0x%02x) 0x%08x", value.dataType, value.data));
break;
}
}
@@ -424,107 +572,107 @@ Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
return new Attribute(*this);
}
void Attribute::PrintMask(std::ostream* out) const {
std::string Attribute::MaskString() const {
if (type_mask == android::ResTable_map::TYPE_ANY) {
*out << "any";
return;
return "any";
}
std::ostringstream out;
bool set = false;
if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "reference";
out << "reference";
}
if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "string";
out << "string";
}
if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "integer";
out << "integer";
}
if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "boolean";
out << "boolean";
}
if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "color";
out << "color";
}
if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "float";
out << "float";
}
if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "dimension";
out << "dimension";
}
if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "fraction";
out << "fraction";
}
if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "enum";
out << "enum";
}
if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
out << "|";
}
*out << "flags";
out << "flags";
}
return out.str();
}
void Attribute::Print(std::ostream* out) const {
*out << "(attr) ";
PrintMask(out);
*out << "(attr) " << MaskString();
if (!symbols.empty()) {
*out << " [" << util::Joiner(symbols, ", ") << "]";

View File

@@ -29,6 +29,7 @@
#include "Resource.h"
#include "StringPool.h"
#include "io/File.h"
#include "text/Printer.h"
#include "util/Maybe.h"
namespace aapt {
@@ -106,6 +107,10 @@ class Value {
// Human readable printout of this value.
virtual void Print(std::ostream* out) const = 0;
// Human readable printout of this value that may omit some information for the sake
// of brevity and readability. Default implementation just calls Print().
virtual void PrettyPrint(text::Printer* printer) const;
friend std::ostream& operator<<(std::ostream& out, const Value& value);
protected:
@@ -162,6 +167,10 @@ struct Reference : public BaseItem<Reference> {
bool Flatten(android::Res_value* out_value) const override;
Reference* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
// Prints the reference without a package name if the package name matches the one given.
void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
};
bool operator<(const Reference&, const Reference&);
@@ -224,6 +233,7 @@ struct String : public BaseItem<String> {
bool Flatten(android::Res_value* out_value) const override;
String* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
};
struct StyledString : public BaseItem<StyledString> {
@@ -274,6 +284,7 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
bool Flatten(android::Res_value* out_value) const override;
BinaryPrimitive* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
};
struct Attribute : public BaseValue<Attribute> {
@@ -294,7 +305,7 @@ struct Attribute : public BaseValue<Attribute> {
bool Equals(const Value* value) const override;
Attribute* Clone(StringPool* new_pool) const override;
void PrintMask(std::ostream* out) const;
std::string MaskString() const;
void Print(std::ostream* out) const override;
bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const;
};

View File

@@ -20,16 +20,14 @@
#include <ostream>
#include <string>
#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"
#include "util/Maybe.h"
namespace aapt {
/**
* Represents a file on disk. Used for logging and
* showing errors.
*/
// Represents a file on disk. Used for logging and showing errors.
struct Source {
std::string path;
Maybe<size_t> line;
@@ -42,7 +40,16 @@ struct Source {
inline Source(const android::StringPiece& path, size_t line)
: path(path.to_string()), line(line) {}
inline Source WithLine(size_t line) const { return Source(path, line); }
inline Source WithLine(size_t line) const {
return Source(path, line);
}
std::string to_string() const {
if (line) {
return ::android::base::StringPrintf("%s:%zd", path.c_str(), line.value());
}
return path;
}
};
//
@@ -50,11 +57,7 @@ struct Source {
//
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
out << source.path;
if (source.line) {
out << ":" << source.line.value();
}
return out;
return out << source.to_string();
}
inline bool operator==(const Source& lhs, const Source& rhs) {

View File

@@ -14,8 +14,10 @@
* limitations under the License.
*/
#include <cinttypes>
#include <vector>
#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"
#include "Debug.h"
@@ -27,9 +29,12 @@
#include "io/FileStream.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "text/Printer.h"
#include "util/Files.h"
using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
@@ -48,16 +53,28 @@ static const char* ResourceFileTypeToString(const ResourceFile::Type& type) {
}
static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset,
size_t len) {
std::cout << "Resource: " << file.name << "\n"
<< "Config: " << file.config << "\n"
<< "Source: " << file.source << "\n"
<< "Type: " << ResourceFileTypeToString(file.type) << "\n"
<< "DataOff: " << offset << "\n"
<< "DataLen: " << len << "\n";
size_t len, Printer* printer) {
printer->Print("Resource: ");
printer->Println(file.name.to_string());
printer->Print("Config: ");
printer->Println(file.config.to_string());
printer->Print("Source: ");
printer->Println(file.source.to_string());
printer->Print("Type: ");
printer->Println(ResourceFileTypeToString(file.type));
printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len));
}
static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
// Use a smaller buffer so that there is less latency for dumping to stdout.
constexpr size_t kStdOutBufferSize = 1024u;
io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
Printer printer(&fout);
DebugPrintTableOptions print_options;
print_options.show_sources = true;
@@ -83,6 +100,8 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
<< "failed to parse table: " << err);
return false;
}
printer.Println("Proto APK");
} else if (io::IFile* file = zip->FindFile("resources.arsc")) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
@@ -95,9 +114,11 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
if (!parser.Parse()) {
return false;
}
printer.Println("Binary APK");
}
Debug::PrintTable(table, print_options);
Debug::PrintTable(table, print_options, &printer);
return true;
}
@@ -118,9 +139,12 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
return false;
}
printer.Println("AAPT2 Container (APC)");
ContainerReaderEntry* entry;
while ((entry = reader.Next()) != nullptr) {
if (entry->Type() == ContainerEntryType::kResTable) {
printer.Println("kResTable");
pb::ResourceTable pb_table;
if (!entry->GetResTable(&pb_table)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
@@ -136,8 +160,11 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
continue;
}
Debug::PrintTable(table, print_options);
printer.Indent();
Debug::PrintTable(table, print_options, &printer);
printer.Undent();
} else if (entry->Type() == ContainerEntryType::kResFile) {
printer.Println("kResFile");
pb::internal::CompiledFile pb_compiled_file;
off64_t offset;
size_t length;
@@ -155,7 +182,9 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
continue;
}
DumpCompiledFile(file, Source(file_path), offset, length);
printer.Indent();
DumpCompiledFile(file, Source(file_path), offset, length, &printer);
printer.Undent();
}
}
return true;

View File

@@ -164,7 +164,7 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc
// Add the inline attribute to the parent.
parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
"@" + new_doc->file.name.ToString()});
"@" + new_doc->file.name.to_string()});
// Delete the subtree.
for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) {

View File

@@ -424,7 +424,7 @@ std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef
// We can ignore the value here.
return util::make_unique<Id>();
default:
diag_->Error(DiagMessage() << "illegal map type '" << ToString(name.type) << "' ("
diag_->Error(DiagMessage() << "illegal map type '" << to_string(name.type) << "' ("
<< (int)name.type << ")");
break;
}

View File

@@ -472,7 +472,7 @@ class PackageFlattener {
expected_type_id++;
}
expected_type_id++;
type_pool_.MakeRef(ToString(type->type));
type_pool_.MakeRef(to_string(type->type));
std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
if (sorted_entries.empty()) {

View File

@@ -284,7 +284,7 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
if (type->id) {
pb_type->mutable_type_id()->set_id(type->id.value());
}
pb_type->set_name(ToString(type->type).to_string());
pb_type->set_name(to_string(type->type).to_string());
for (const std::unique_ptr<ResourceEntry>& entry : type->entries) {
pb::Entry* pb_entry = pb_type->add_entry();
@@ -328,7 +328,7 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref)
pb_ref->set_id(ref.id.value_or_default(ResourceId(0x0)).id);
if (ref.name) {
pb_ref->set_name(ref.name.value().ToString());
pb_ref->set_name(ref.name.value().to_string());
}
pb_ref->set_private_(ref.private_reference);
@@ -523,14 +523,14 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) {
}
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
out_file->set_resource_name(file.name.ToString());
out_file->set_resource_name(file.name.to_string());
out_file->set_source_path(file.source.path);
out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
SerializeConfig(file.config, out_file->mutable_config());
for (const SourcedResourceName& exported : file.exported_symbols) {
pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
pb_symbol->set_resource_name(exported.name.ToString());
pb_symbol->set_resource_name(exported.name.to_string());
pb_symbol->mutable_source()->set_line_number(exported.line);
}
}

View File

@@ -26,6 +26,7 @@
#include "android-base/utf8.h"
using ::android::base::SystemErrorCodeToString;
using ::android::base::unique_fd;
namespace aapt {
namespace io {
@@ -100,7 +101,13 @@ std::string FileInputStream::GetError() const {
}
FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
: FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) {
: FileOutputStream(unique_fd(::android::base::utf8::open(path.c_str(), mode)),
buffer_capacity) {
}
FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity)
: FileOutputStream(fd.get(), buffer_capacity) {
owned_fd_ = std::move(fd);
}
FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
@@ -118,7 +125,7 @@ FileOutputStream::~FileOutputStream() {
}
bool FileOutputStream::Next(void** data, size_t* size) {
if (fd_ == -1 || HadError()) {
if (HadError()) {
return false;
}
@@ -159,7 +166,8 @@ bool FileOutputStream::FlushImpl() {
ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
if (n < 0) {
error_ = SystemErrorCodeToString(errno);
fd_.reset();
owned_fd_.reset();
fd_ = -1;
buffer_.reset();
return false;
}

View File

@@ -29,12 +29,15 @@
namespace aapt {
namespace io {
constexpr size_t kDefaultBufferCapacity = 4096u;
class FileInputStream : public InputStream {
public:
explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096);
explicit FileInputStream(const std::string& path,
size_t buffer_capacity = kDefaultBufferCapacity);
// Takes ownership of `fd`.
explicit FileInputStream(int fd, size_t buffer_capacity = 4096);
// Take ownership of `fd`.
explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
bool Next(const void** data, size_t* size) override;
@@ -61,10 +64,14 @@ class FileInputStream : public InputStream {
class FileOutputStream : public OutputStream {
public:
explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
size_t buffer_capacity = 4096);
size_t buffer_capacity = kDefaultBufferCapacity);
// Does not take ownership of `fd`.
explicit FileOutputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
// Takes ownership of `fd`.
explicit FileOutputStream(int fd, size_t buffer_capacity = 4096);
explicit FileOutputStream(android::base::unique_fd fd,
size_t buffer_capacity = kDefaultBufferCapacity);
~FileOutputStream();
@@ -86,7 +93,8 @@ class FileOutputStream : public OutputStream {
bool FlushImpl();
android::base::unique_fd fd_;
android::base::unique_fd owned_fd_;
int fd_;
std::string error_;
std::unique_ptr<uint8_t[]> buffer_;
size_t buffer_capacity_;

View File

@@ -87,10 +87,8 @@ TEST(FileOutputStreamTest, NextAndBackup) {
const std::string input = "this is a cool string";
TemporaryFile file;
int fd = file.release();
// FileOutputStream takes ownership.
FileOutputStream out(fd, 10u);
FileOutputStream out(file.fd, 10u);
ASSERT_FALSE(out.HadError());
EXPECT_THAT(out.ByteCount(), Eq(0u));
@@ -118,10 +116,10 @@ TEST(FileOutputStreamTest, NextAndBackup) {
ASSERT_TRUE(out.Flush());
lseek64(fd, 0, SEEK_SET);
lseek64(file.fd, 0, SEEK_SET);
std::string actual;
ASSERT_TRUE(android::base::ReadFdToString(fd, &actual));
ASSERT_TRUE(android::base::ReadFdToString(file.fd, &actual));
EXPECT_THAT(actual, StrEq(input));
}

View File

@@ -18,6 +18,7 @@
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
using ::android::StringPiece;
using ::google::protobuf::io::ZeroCopyOutputStream;
namespace aapt {
@@ -93,6 +94,25 @@ bool Copy(OutputStream* out, InputStream* in) {
return !in->HadError();
}
bool Copy(OutputStream* out, const StringPiece& in) {
const char* in_buffer = in.data();
size_t in_len = in.size();
while (in_len != 0) {
void* out_buffer;
size_t out_len;
if (!out->Next(&out_buffer, &out_len)) {
return false;
}
const size_t bytes_to_copy = in_len < out_len ? in_len : out_len;
memcpy(out_buffer, in_buffer, bytes_to_copy);
out->BackUp(out_len - bytes_to_copy);
in_buffer += bytes_to_copy;
in_len -= bytes_to_copy;
}
return true;
}
bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
OutputStreamAdaptor adaptor(out);
return Copy(&adaptor, in);

View File

@@ -42,6 +42,7 @@ bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite*
// Copies the data from in to out. Returns false if there was an error.
// If there was an error, check the individual streams' HadError/GetError methods.
bool Copy(OutputStream* out, InputStream* in);
bool Copy(OutputStream* out, const ::android::StringPiece& in);
bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
class OutputStreamAdaptor : public io::OutputStream {

View File

@@ -461,7 +461,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso
}
if (out_rewrite_method != nullptr) {
const StringPiece& type_str = ToString(name.type);
const StringPiece& type_str = to_string(name.type);
out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
type_str.data(), field_name.data(),
type_str.data(), field_name.data()));
@@ -584,7 +584,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
(options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty);
to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
rewrite_method.get(), out_r_txt)) {
return false;

View File

@@ -289,7 +289,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
const android::ResTable& table = assets_.getResources(false);
const std::u16string package16 = util::Utf8ToUtf16(name.package);
const std::u16string type16 = util::Utf8ToUtf16(ToString(name.type));
const std::u16string type16 = util::Utf8ToUtf16(to_string(name.type));
const std::u16string entry16 = util::Utf8ToUtf16(name.entry);
uint32_t type_spec_flags = 0;

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "text/Printer.h"
#include <algorithm>
#include "io/Util.h"
using ::aapt::io::OutputStream;
using ::android::StringPiece;
namespace aapt {
namespace text {
void Printer::Println(const StringPiece& str) {
Print(str);
Print("\n");
}
void Printer::Println() {
Print("\n");
}
void Printer::Print(const StringPiece& str) {
if (error_) {
return;
}
auto remaining_str_begin = str.begin();
const auto remaining_str_end = str.end();
while (remaining_str_end != remaining_str_begin) {
// Find the next new-line.
const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
// We will copy the string up until the next new-line (or end of string).
const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
if (!str_to_copy.empty()) {
if (needs_indent_) {
for (int i = 0; i < indent_level_; i++) {
if (!io::Copy(out_, " ")) {
error_ = true;
return;
}
}
needs_indent_ = false;
}
if (!io::Copy(out_, str_to_copy)) {
error_ = true;
return;
}
}
// If we found a new-line.
if (new_line_iter != remaining_str_end) {
if (!io::Copy(out_, "\n")) {
error_ = true;
return;
}
needs_indent_ = true;
// Ok to increment iterator here because we know that the '\n' character is one byte.
remaining_str_begin = new_line_iter + 1;
} else {
remaining_str_begin = new_line_iter;
}
}
}
void Printer::Indent() {
++indent_level_;
}
void Printer::Undent() {
--indent_level_;
}
} // namespace text
} // namespace aapt

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAPT_TEXT_PRINTER_H
#define AAPT_TEXT_PRINTER_H
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
#include "io/Io.h"
namespace aapt {
namespace text {
// An indenting Printer that helps write formatted text to the OutputStream.
class Printer {
public:
explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
}
void Print(const ::android::StringPiece& str);
void Println(const ::android::StringPiece& str);
void Println();
void Indent();
void Undent();
private:
DISALLOW_COPY_AND_ASSIGN(Printer);
::aapt::io::OutputStream* out_;
int indent_level_ = 0;
bool needs_indent_ = false;
bool error_ = false;
};
} // namespace text
} // namespace aapt
#endif // AAPT_TEXT_PRINTER_H

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "text/Printer.h"
#include "io/StringStream.h"
#include "test/Test.h"
using ::aapt::io::StringOutputStream;
using ::android::StringPiece;
using ::testing::StrEq;
namespace aapt {
namespace text {
TEST(PrinterTest, PrintsToStreamWithIndents) {
std::string result;
StringOutputStream out(&result);
Printer printer(&out);
printer.Print("Hello");
out.Flush();
EXPECT_THAT(result, StrEq("Hello"));
printer.Println();
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n"));
// This shouldn't print anything yet.
printer.Indent();
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n"));
// Now we should see the indent.
printer.Print("world!");
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n world!"));
printer.Println(" What a\nlovely day.");
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\n"));
// This shouldn't print anything yet.
printer.Undent();
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\n"));
printer.Println("Isn't it?");
out.Flush();
EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\nIsn't it?\n"));
}
} // namespace text
} // namespace aapt