diff options
Diffstat (limited to 'tools')
38 files changed, 295 insertions, 1024 deletions
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index df878899fa28..cac4edd8db21 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -265,6 +265,16 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& ValueHeadlinePrinter headline_printer(package.name, printer); ValueBodyPrinter body_printer(package.name, printer); + auto& dynamicRefTable = table.GetReferencedPackages(); + if (!dynamicRefTable.empty()) { + printer->Println(StringPrintf("DynamicRefTable entryCount=%d", int(dynamicRefTable.size()))); + printer->Indent(); + for (auto&& [id, name] : dynamicRefTable) { + printer->Println(StringPrintf("0x%02x -> %s", id, name.c_str())); + } + printer->Undent(); + } + printer->Print("Package name="); printer->Print(package.name); if (package.id) { diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index fa9a98f136cb..6af39b739e9b 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -800,7 +800,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub // Process the raw value. std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute( - xmlsub_tree.raw_value, type_mask, on_create_reference); + &diag, xmlsub_tree.raw_value, type_mask, on_create_reference); if (processed_item) { // Fix up the reference. if (auto ref = ValueCast<Reference>(processed_item.get())) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index bb286a8abdaa..61e399c7ab68 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -307,6 +307,11 @@ class ResourceTable { // order. ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const; + using ReferencedPackages = std::map<uint8_t, std::string>; + const ReferencedPackages& GetReferencedPackages() const { + return included_packages_; + } + struct SearchResult { ResourceTablePackage* package; ResourceTableType* type; @@ -342,7 +347,7 @@ class ResourceTable { // Set of dynamic packages that this table may reference. Their package names get encoded // into the resources.arsc along with their compile-time assigned IDs. - std::map<size_t, std::string> included_packages_; + ReferencedPackages included_packages_; private: DISALLOW_COPY_AND_ASSIGN(ResourceTable); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 5a118a902963..d358df98ada6 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -619,7 +619,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) { } std::unique_ptr<Item> TryParseItemForAttribute( - StringPiece value, uint32_t type_mask, + android::IDiagnostics* diag, StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; @@ -670,8 +670,32 @@ std::unique_ptr<Item> TryParseItemForAttribute( // Try parsing this as a float. auto floating_point = TryParseFloat(value); if (floating_point) { + // Only check if the parsed result lost precision when the parsed item is + // android::Res_value::TYPE_FLOAT and there is other possible types saved in type_mask, like + // ResTable_map::TYPE_INTEGER. if (type_mask & AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) { - return std::move(floating_point); + const bool mayOnlyBeFloat = (type_mask & ~float_mask) == 0; + const bool parsedAsFloat = floating_point->value.dataType == android::Res_value::TYPE_FLOAT; + if (!mayOnlyBeFloat && parsedAsFloat) { + float f = reinterpret_cast<float&>(floating_point->value.data); + std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(value)); + double d; + if (android::ResTable::stringToDouble(str16.data(), str16.size(), d)) { + // Parse as a float only if the difference between float and double parsed from the + // same string is smaller than 1, otherwise return as raw string. + if (fabs(f - d) < 1) { + return std::move(floating_point); + } else { + if (diag->IsVerbose()) { + diag->Note(android::DiagMessage() + << "precision lost greater than 1 while parsing float " << value + << ", return a raw string"); + } + } + } + } else { + return std::move(floating_point); + } } } } @@ -683,12 +707,12 @@ std::unique_ptr<Item> TryParseItemForAttribute( * allows. */ std::unique_ptr<Item> TryParseItemForAttribute( - StringPiece str, const Attribute* attr, + android::IDiagnostics* diag, StringPiece str, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; const uint32_t type_mask = attr->type_mask; - auto value = TryParseItemForAttribute(str, type_mask, on_create_reference); + auto value = TryParseItemForAttribute(diag, str, type_mask, on_create_reference); if (value) { return value; } diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index f30f4acfec7a..50fc87900162 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -200,11 +200,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, * reference to an ID that must be created (@+id/foo). */ std::unique_ptr<Item> TryParseItemForAttribute( - android::StringPiece value, const Attribute* attr, + android::IDiagnostics* diag, android::StringPiece value, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference = {}); std::unique_ptr<Item> TryParseItemForAttribute( - android::StringPiece value, uint32_t type_mask, + android::IDiagnostics* diag, android::StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference = {}); uint32_t AndroidTypeToAttributeTypeMask(uint16_t type); diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 568871a4d66e..4cba04de72ee 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -217,17 +217,46 @@ TEST(ResourceUtilsTest, EmptyIsBinaryPrimitive) { } TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) { - EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12\n ", ResTable_map::TYPE_INTEGER), + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12\n ", + ResTable_map::TYPE_INTEGER), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_DEC, 12u)))); - EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" true\n ", ResTable_map::TYPE_BOOLEAN), + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " true\n ", + ResTable_map::TYPE_BOOLEAN), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_BOOLEAN, 0xffffffffu)))); const float expected_float = 12.0f; const uint32_t expected_float_flattened = *(uint32_t*)&expected_float; - EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12.0\n ", ResTable_map::TYPE_FLOAT), + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12.0\n ", + ResTable_map::TYPE_FLOAT), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened)))); } +TEST(ResourceUtilsTest, FloatAndBigIntegerParsedCorrectly) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const float expected_float = 0.125f; + const uint32_t expected_float_flattened = *(uint32_t*)&expected_float; + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "0.125", + ResTable_map::TYPE_FLOAT), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened)))); + + const float special_float = 1.0f; + const uint32_t special_float_flattened = *(uint32_t*)&special_float; + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1.0", + ResTable_map::TYPE_FLOAT), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, special_float_flattened)))); + + EXPECT_EQ(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776", + ResTable_map::TYPE_INTEGER), + std::unique_ptr<Item>(nullptr)); + + const float big_float = 1099511627776.0f; + const uint32_t big_flattened = *(uint32_t*)&big_float; + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776", + ResTable_map::TYPE_FLOAT), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, big_flattened)))); +} + TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) { EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(std::optional<int>(10000))); EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q.fingerprint"), Eq(std::optional<int>(10000))); diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index a5754e0d168f..166b01bd9154 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -439,6 +439,21 @@ static std::string ComplexToString(uint32_t complex_value, bool fraction) { return str; } +// This function is designed to using different specifier to print different floats, +// which can print more accurate format rather than using %g only. +const char* BinaryPrimitive::DecideFormat(float f) { + // if the float is either too big or too tiny, print it in scientific notation. + // eg: "10995116277760000000000" to 1.099512e+22, "0.00000000001" to 1.000000e-11 + if (fabs(f) > std::numeric_limits<int64_t>::max() || fabs(f) < 1e-10) { + return "%e"; + // Else if the number is an integer exactly, print it without trailing zeros. + // eg: "1099511627776" to 1099511627776 + } else if (int64_t(f) == f) { + return "%.0f"; + } + return "%g"; +} + void BinaryPrimitive::PrettyPrint(Printer* printer) const { using ::android::Res_value; switch (value.dataType) { @@ -470,7 +485,9 @@ void BinaryPrimitive::PrettyPrint(Printer* printer) const { break; case Res_value::TYPE_FLOAT: - printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data))); + float f; + f = *reinterpret_cast<const float*>(&value.data); + printer->Print(StringPrintf(DecideFormat(f), f)); break; case Res_value::TYPE_DIMENSION: diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 6f9dccbd3bcc..5192c2be1f98 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -284,6 +284,7 @@ struct BinaryPrimitive : public TransformableItem<BinaryPrimitive, BaseItem<Bina bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; void Print(std::ostream* out) const override; + static const char* DecideFormat(float f); void PrettyPrint(text::Printer* printer) const override; }; diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index a7c5479b56fd..a766bd437120 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -26,8 +26,8 @@ using android::StringPiece; namespace aapt { static ApiVersion sDevelopmentSdkLevel = 10000; -static const auto sDevelopmentSdkCodeNames = - std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake"}); +static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>( + {"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake", "VanillaIceCream"}); static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x021c, 1}, diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 03f9715fb265..d2ea59958f69 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -597,6 +597,7 @@ class CompileContext : public IAaptContext { void SetVerbose(bool val) { verbose_ = val; + diagnostics_->SetVerbose(val); } bool IsVerbose() override { diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 71b08022f688..864af06f187e 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -112,6 +112,7 @@ class DumpContext : public IAaptContext { void SetVerbose(bool val) { verbose_ = val; + diagnostics_.SetVerbose(val); } int GetMinSdkVersion() override { diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 97404fc69af2..eb4e38c5f35f 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -148,6 +148,7 @@ class LinkContext : public IAaptContext { void SetVerbose(bool val) { verbose_ = val; + diagnostics_->SetVerbose(val); } int GetMinSdkVersion() override { diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 28fcc1a4800e..7096f5cc54e3 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -441,8 +441,8 @@ static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& R"(<resources> <public type="attr" name="finalized_res" id="0x01010001"/> - <!-- S staged attributes (support staged resources in the same type id) --> - <staging-public-group type="attr" first-id="0x01010050"> + <!-- S staged attributes (Not support staged resources in the same type id) --> + <staging-public-group type="attr" first-id="0x01fc0050"> <public name="staged_s_res" /> </staging-public-group> @@ -480,8 +480,8 @@ static void BuildFinalizedSDK(const std::string& apk_path, const std::string& ja <public type="attr" name="staged_s2_res" id="0x01010003"/> <public type="string" name="staged_s_string" id="0x01020000"/> - <!-- S staged attributes (support staged resources in the same type id) --> - <staging-public-group-final type="attr" first-id="0x01010050"> + <!-- S staged attributes (Not support staged resources in the same type id) --> + <staging-public-group-final type="attr" first-id="0x01fc0050"> <public name="staged_s_res" /> </staging-public-group-final> @@ -551,7 +551,7 @@ TEST_F(LinkTest, StagedAndroidApi) { EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;")); EXPECT_THAT( android_r_contents, - HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }")); + HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01fc0050; }")); EXPECT_THAT( android_r_contents, HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }")); @@ -575,7 +575,7 @@ TEST_F(LinkTest, StagedAndroidApi) { android::AssetManager2 am; auto android_asset = android::ApkAssets::Load(android_apk); ASSERT_THAT(android_asset, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset})); auto result = am.GetResourceId("android:attr/finalized_res"); ASSERT_TRUE(result.has_value()); @@ -583,7 +583,7 @@ TEST_F(LinkTest, StagedAndroidApi) { result = am.GetResourceId("android:attr/staged_s_res"); ASSERT_TRUE(result.has_value()); - EXPECT_THAT(*result, Eq(0x01010050)); + EXPECT_THAT(*result, Eq(0x01fc0050)); result = am.GetResourceId("android:string/staged_s_string"); ASSERT_TRUE(result.has_value()); @@ -631,7 +631,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) { auto app_against_non_final = android::ApkAssets::Load(app_apk); ASSERT_THAT(android_asset, NotNull()); ASSERT_THAT(app_against_non_final, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_non_final})); auto result = am.GetResourceId("android:attr/finalized_res"); ASSERT_TRUE(result.has_value()); @@ -667,7 +667,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) { auto app_against_final = android::ApkAssets::Load(app_apk_respin); ASSERT_THAT(app_against_final, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_final})); { auto style = am.GetBag(0x7f020000); diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index dbe79701bf5c..f045dad6d11a 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -101,6 +101,7 @@ class OptimizeContext : public IAaptContext { void SetVerbose(bool val) { verbose_ = val; + diagnostics_.SetVerbose(val); } void SetMinSdkVersion(int sdk_version) { diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index b3f98a9d3e30..5421abde3689 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -37,6 +37,7 @@ using Result = expected<T, std::string>; template <typename Id, typename Key> struct NextIdFinder { + std::map<Id, Key> pre_assigned_ids_; explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){}; // Attempts to reserve an identifier for the specified key. @@ -55,7 +56,6 @@ struct NextIdFinder { Id next_id_; bool next_id_called_ = false; bool exhausted_ = false; - std::map<Id, Key> pre_assigned_ids_; typename std::map<Id, Key>::iterator next_preassigned_id_; }; @@ -158,7 +158,7 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { } if (assigned_id_map_) { - // Reserve all the IDs mentioned in the stable ID map. That way we won't assig IDs that were + // 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& stable_id_entry : *assigned_id_map_) { const ResourceName& pre_assigned_name = stable_id_entry.first; @@ -191,6 +191,11 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { } namespace { +static const std::string_view staged_type_overlap_error = + "Staged public resource type IDs have conflict with non staged public resources type " + "IDs, please restart staged resource type ID assignment at 0xff in public-staging.xml " + "and also delete all the overlapping groups in public-final.xml"; + template <typename Id, typename Key> Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) { CHECK(!next_id_called_) << "ReserveId cannot be called after NextId"; @@ -282,8 +287,20 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, // another type. auto assign_result = type_id_finder_.ReserveId(key, id.type_id()); if (!assign_result.has_value()) { - diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name - << " because type " << assign_result.error()); + auto pre_assigned_type = type_id_finder_.pre_assigned_ids_[id.type_id()].type; + bool pre_assigned_type_staged = + non_staged_type_ids_.find(pre_assigned_type) == non_staged_type_ids_.end(); + auto hex_type_id = fmt::format("{:#04x}", (int)id.type_id()); + bool current_type_staged = visibility.staged_api; + diag->Error(android::DiagMessage() + << "can't assign type ID " << hex_type_id << " to " + << (current_type_staged ? "staged type " : "non staged type ") << name.type.type + << " because this type ID have been assigned to " + << (pre_assigned_type_staged ? "staged type " : "non staged type ") + << pre_assigned_type); + if (pre_assigned_type_staged || current_type_staged) { + diag->Error(android::DiagMessage() << staged_type_overlap_error); + } return false; } type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first; @@ -298,6 +315,20 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, << " because type already has ID " << std::hex << (int)id.type_id()); return false; } + } else { + // Ensure that staged public resources cannot have the same type name and type id with + // non staged public resources. + auto non_staged_type = non_staged_type_ids_.find(name.type.type); + if (non_staged_type != non_staged_type_ids_.end() && non_staged_type->second == id.type_id()) { + diag->Error( + android::DiagMessage() + << "can`t assign type ID " << fmt::format("{:#04x}", (int)id.type_id()) + << " to staged type " << name.type.type << " because type ID " + << fmt::format("{:#04x}", (int)id.type_id()) + << " already has been assigned to a non staged resource type with the same type name"); + diag->Error(android::DiagMessage() << staged_type_overlap_error); + return false; + } } auto assign_result = type->second.ReserveId(name, id); diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index 8911dad39470..ce45b7c1df04 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -117,14 +117,28 @@ TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) { } TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) { - auto table = test::ResourceTableBuilder() - .AddSimple("android:attr/foo", ResourceId(0x01050000)) - .AddSimple("android:attr/bar", ResourceId(0x01ff0006)) - .Add(NewResourceBuilder("android:attr/staged_baz") - .SetId(0x01ff0000) - .SetVisibility({.staged_api = true}) - .Build()) - .Build(); + auto table = + test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01050000)) + .AddSimple("android:attr/bar", ResourceId(0x01ff0006)) + .Add(NewResourceBuilder("android:attr/staged_baz") + .SetId(0x01ff0000) + .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic}) + .Build()) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} + +TEST_F(IdAssignerTests, FailWhenTypeHaveBothStagedAndNonStagedIds) { + auto table = + test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01010000)) + .Add(NewResourceBuilder("android:bool/staged_baz") + .SetId(0x01010001) + .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic}) + .Build()) + .Build(); IdAssigner assigner; ASSERT_FALSE(assigner.Consume(context.get(), table.get())); } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 8c594ba553a0..a1953c6966af 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -68,9 +68,8 @@ struct OverlayableChunk { class PackageFlattener { public: PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package, - const std::map<size_t, std::string>* shared_libs, - SparseEntriesMode sparse_entries, - bool compact_entries, + const ResourceTable::ReferencedPackages* shared_libs, + SparseEntriesMode sparse_entries, bool compact_entries, bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions, bool deduplicate_entry_values) @@ -548,7 +547,7 @@ class PackageFlattener { IAaptContext* context_; android::IDiagnostics* diag_; const ResourceTablePackageView package_; - const std::map<size_t, std::string>* shared_libs_; + const ResourceTable::ReferencedPackages* shared_libs_; SparseEntriesMode sparse_entries_; bool compact_entries_; android::StringPool type_pool_; diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 09ef9bddd3bd..e1a3013c07bb 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -916,7 +916,7 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, } break; case pb::Primitive::kIntDecimalValue: { val.dataType = android::Res_value::TYPE_INT_DEC; - val.data = static_cast<uint32_t>(pb_prim.int_decimal_value()); + val.data = static_cast<int32_t>(pb_prim.int_decimal_value()); } break; case pb::Primitive::kIntHexadecimalValue: { val.dataType = android::Res_value::TYPE_INT_HEX; diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index afb83562b129..fa8860ff69eb 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -250,6 +250,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { } TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); xml::Element element; element.line_number = 22; element.column_number = 23; @@ -269,8 +270,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { attr.namespace_uri = xml::kSchemaAndroid; attr.value = "23dp"; attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000)); - attr.compiled_value = - ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION); + attr.compiled_value = ResourceUtils::TryParseItemForAttribute( + context->GetDiagnostics(), attr.value, android::ResTable_map::TYPE_DIMENSION); attr.compiled_value->SetSource(android::Source().WithLine(25)); element.attributes.push_back(std::move(attr)); diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 9dadfb26a3f8..c69b32513167 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -164,8 +164,8 @@ std::unique_ptr<Item> ReferenceLinkerTransformer::TransformItem(const Reference* std::unique_ptr<Item> ReferenceLinkerTransformer::ParseValueWithAttribute( std::unique_ptr<Item> value, const Attribute* attr) { if (RawString* raw_string = ValueCast<RawString>(value.get())) { - std::unique_ptr<Item> transformed = - ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr); + std::unique_ptr<Item> transformed = ResourceUtils::TryParseItemForAttribute( + context_->GetDiagnostics(), *raw_string->value, attr); // If we could not parse as any specific type, try a basic STRING. if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) { diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index d2e9bd770a31..aec7ceb82c0f 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -90,7 +90,8 @@ class XmlVisitor : public xml::PackageAwareVisitor { attribute = &attr.compiled_attribute.value().attribute; } - attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, attribute); + attr.compiled_value = ResourceUtils::TryParseItemForAttribute(context_->GetDiagnostics(), + attr.value, attribute); if (attr.compiled_value) { // With a compiledValue, we must resolve the reference and assign it an ID. attr.compiled_value->SetSource(source); diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp index 940cf1096f92..b3a915c9b604 100644 --- a/tools/aapt2/optimize/Obfuscator_test.cpp +++ b/tools/aapt2/optimize/Obfuscator_test.cpp @@ -300,10 +300,11 @@ TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) { ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(), getProtocolBufferTableUnderTest().get())); - obfuscator.WriteObfuscationMap("obfuscated_map.pb"); + const auto map_path = testing::TempDir() + "/obfuscated_map.pb"; + ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path)); std::string pbOut; - android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */); + ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */)); EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml")); EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png")); EXPECT_THAT(pbOut, HasSubstr("mycolor")); @@ -328,10 +329,11 @@ TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) { ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(), getProtocolBufferTableUnderTest().get())); - obfuscator.WriteObfuscationMap("obfuscated_map.pb"); + const auto map_path = testing::TempDir() + "/obfuscated_map.pb"; + ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path)); std::string pbOut; - android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */); + ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */)); ASSERT_THAT(pbOut, Eq("")); } diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index bca62da447b0..d78baf9ffeb4 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -220,15 +220,9 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) { TRACE_CALL(); - if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) { + if (auto apk = ApkAssets::Load(path.data())) { apk_assets_.push_back(std::move(apk)); - - std::vector<const ApkAssets*> apk_assets; - for (const std::unique_ptr<const ApkAssets>& apk_asset : apk_assets_) { - apk_assets.push_back(apk_asset.get()); - } - - asset_manager_.SetApkAssets(apk_assets); + asset_manager_.SetApkAssets(apk_assets_); return true; } return false; @@ -251,7 +245,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, return true; } - for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) { + for (auto&& assets : apk_assets_) { for (const std::unique_ptr<const android::LoadedPackage>& loaded_package : assets->GetLoadedArsc()->GetPackages()) { if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) { @@ -266,10 +260,11 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( android::AssetManager2& am, ResourceId id) { using namespace android; - if (am.GetApkAssets().empty()) { + if (am.GetApkAssetsCount() == 0) { return {}; } + auto op = am.StartOperation(); auto bag_result = am.GetBag(id.id); if (!bag_result.has_value()) { return nullptr; diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index b09ff702ca58..36eb0bab6046 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -207,8 +207,8 @@ class AssetManagerSymbolSource : public ISymbolSource { } private: + std::vector<android::AssetManager2::ApkAssetsPtr> apk_assets_; android::AssetManager2 asset_manager_; - std::vector<std::unique_ptr<const android::ApkAssets>> apk_assets_; DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); }; diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index dd266ffe0f16..a2c1d6b4f784 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -383,13 +383,11 @@ static bool generatePrivacyFlags(const Descriptor* descriptor, const Destination if (allDefaults) return false; emptyline(); - int policyCount = 0; printf("Privacy* %s[] = {\n", messageName.c_str()); for (size_t i=0; i<fieldsInOrder.size(); i++) { const FieldDescriptor* field = fieldsInOrder[i]; if (hasDefaultFlags[i]) continue; // NOLINT(clang-analyzer-core.uninitialized.Branch) printf(" &%s,\n", getFieldName(field).c_str()); - policyCount++; } printf(" NULL };\n"); emptyline(); diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt index dcfbe953f955..e03d92ab44a0 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt @@ -72,5 +72,78 @@ val EXCLUDED_CPP_INTERFACES = listOf( "Status", "IThermalService", "IPowerManager", - "ITunerResourceManager" + "ITunerResourceManager", + // b/278147400 + "IActivityManager", + "IUidObserver", + "IDrm", + "IVsyncCallback", + "IVsyncService", + "ICallback", + "IIPCTest", + "ISafeInterfaceTest", + "IGpuService", + "IConsumerListener", + "IGraphicBufferConsumer", + "ITransactionComposerListener", + "SensorEventConnection", + "SensorServer", + "ICamera", + "ICameraClient", + "ICameraRecordingProxy", + "ICameraRecordingProxyListener", + "ICrypto", + "IOMXObserver", + "IStreamListener", + "IStreamSource", + "IAudioService", + "IDataSource", + "IDrmClient", + "IMediaCodecList", + "IMediaDrmService", + "IMediaExtractor", + "IMediaExtractorService", + "IMediaHTTPConnection", + "IMediaHTTPService", + "IMediaLogService", + "IMediaMetadataRetriever", + "IMediaMetricsService", + "IMediaPlayer", + "IMediaPlayerClient", + "IMediaPlayerService", + "IMediaRecorder", + "IMediaRecorderClient", + "IMediaResourceMonitor", + "IMediaSource", + "IRemoteDisplay", + "IRemoteDisplayClient", + "IResourceManagerClient", + "IResourceManagerService", + "IComplexTypeInterface", + "IPermissionController", + "IPingResponder", + "IProcessInfoService", + "ISchedulingPolicyService", + "IStringConstants", + "IObbActionListener", + "IStorageEventListener", + "IStorageManager", + "IStorageShutdownObserver", + "IPersistentVrStateCallbacks", + "IVrManager", + "IVrStateCallbacks", + "ISurfaceComposer", + "IMemory", + "IMemoryHeap", + "IProcfsInspector", + "IAppOpsCallback", + "IAppOpsService", + "IBatteryStats", + "IResultReceiver", + "IShellCallback", + "IDrmManagerService", + "IDrmServiceListener", + "IAAudioClient", + "IAAudioService", + "VtsFuzzer", ) diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 0baac2c7aacf..0a66cd510353 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -40,6 +40,8 @@ import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod import org.jetbrains.uast.toUElement +import java.util.EnumSet + /** * Lint Detector that ensures that any method overriding a method annotated * with @EnforcePermission is also annotated with the exact same annotation. @@ -206,7 +208,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) @@ -219,7 +221,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) } diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt index df13af516514..cbbf91b49ded 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt @@ -34,6 +34,8 @@ import org.jetbrains.uast.UExpression import org.jetbrains.uast.UMethod import org.jetbrains.uast.skipParenthesizedExprDown +import java.util.EnumSet + class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { override fun getApplicableUastTypes(): List<Class<out UElement?>> = listOf(UMethod::class.java) @@ -117,7 +119,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionHelperDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) @@ -130,7 +132,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt index c7be36efd991..22f749eee36b 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt @@ -53,10 +53,9 @@ class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() { lintFix ) - // TODO(b/265014041): turn on errors once all code that would cause one is fixed - // if (enforcePermissionFix.errorLevel) { - // incident.overrideSeverity(Severity.ERROR) - // } + if (enforcePermissionFix.errorLevel) { + incident.overrideSeverity(Severity.ERROR) + } context.report(incident) } diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt index 6b8e72cf9222..9170e752934f 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt @@ -51,10 +51,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -168,10 +168,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] mContext.enforceCallingOrSelfPermission( ^ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -209,10 +209,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -252,10 +252,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:10: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:10: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] mContext.enforceCallingOrSelfPermission( ^ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -414,10 +414,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:14: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:14: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] helper(); ~~~~~~~~~ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -506,10 +506,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:16: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:16: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] mContext.enforceCallingOrSelfPermission("FOO", "foo"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -558,10 +558,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:19: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:19: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] helperHelper(); ~~~~~~~~~~~~~~~ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( @@ -599,10 +599,10 @@ class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { .run() .expect( """ - src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo") ^ - 0 errors, 1 warnings + 1 errors, 0 warnings """ ) .expectFixDiffs( diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index 039bb4e2788c..46745e995f64 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -11,7 +11,7 @@ java_library_host { name: "protologtool-lib", srcs: [ "src/com/android/protolog/tool/**/*.kt", - ":protolog-common-src", + ":protolog-common-no-android-src", ], static_libs: [ "javaparser", diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp deleted file mode 100644 index 0b6dba626794..000000000000 --- a/tools/xmlpersistence/Android.bp +++ /dev/null @@ -1,20 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -java_binary_host { - name: "xmlpersistence_cli", - manifest: "manifest.txt", - srcs: [ - "src/**/*.kt", - ], - static_libs: [ - "javaparser-symbol-solver", - "javapoet", - ], -} diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS deleted file mode 100644 index 4f4d06a32676..000000000000 --- a/tools/xmlpersistence/OWNERS +++ /dev/null @@ -1 +0,0 @@ -zhanghai@google.com diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt deleted file mode 100644 index 6d9771998efc..000000000000 --- a/tools/xmlpersistence/manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: MainKt diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt deleted file mode 100644 index 8e62388c860f..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Generator.kt +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.FieldSpec -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterSpec -import com.squareup.javapoet.TypeSpec -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.nio.charset.StandardCharsets -import java.time.Year -import java.util.Objects -import javax.lang.model.element.Modifier - -// JavaPoet only supports line comments, and can't add a newline after file level comments. -val FILE_HEADER = """ - /* - * Copyright (C) ${Year.now().value} 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. - */ - - // Generated by xmlpersistence. DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // @formatter:off -""".trimIndent() + "\n\n" - -private val atomicFileType = ClassName.get("android.util", "AtomicFile") - -fun generate(persistence: PersistenceInfo): JavaFile { - val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type } - val type = TypeSpec.classBuilder(persistence.name) - .addJavadoc( - """ - Generated class implementing XML persistence for${'$'}W{@link $1T}. - <p> - This class provides atomicity for persistence via {@link $2T}, however it does not provide - thread safety, so please bring your own synchronization mechanism. - """.trimIndent(), persistence.root.type, atomicFileType - ) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addField(generateFileField()) - .addMethod(generateConstructor()) - .addMethod(generateReadMethod(persistence.root)) - .addMethod(generateParseMethod(persistence.root)) - .addMethods(distinctClassFields.map { generateParseClassMethod(it) }) - .addMethod(generateWriteMethod(persistence.root)) - .addMethod(generateSerializeMethod(persistence.root)) - .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) }) - .addMethod(generateDeleteMethod()) - .build() - return JavaFile.builder(persistence.root.type.packageName(), type) - .skipJavaLangImports(true) - .indent(" ") - .build() -} - -private val nonNullType = ClassName.get("android.annotation", "NonNull") - -private fun generateFileField(): FieldSpec = - FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL) - .addAnnotation(nonNullType) - .build() - -private fun generateConstructor(): MethodSpec = - MethodSpec.constructorBuilder() - .addJavadoc( - """ - Create an instance of this class. - - @param file the XML file for persistence - """.trimIndent() - ) - .addModifiers(Modifier.PUBLIC) - .addParameter( - ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build() - ) - .addStatement("mFile = new \$1T(file)", atomicFileType) - .build() - -private val nullableType = ClassName.get("android.annotation", "Nullable") - -private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser") - -private val xmlType = ClassName.get("android.util", "Xml") - -private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException") - -private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("read") - .addJavadoc( - """ - Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile. - - @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist - @throws IllegalArgumentException if an error occurred while reading - """.trimIndent(), rootField.type - ) - .addAnnotation(nullableType) - .addModifiers(Modifier.PUBLIC) - .returns(rootField.type) - .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) { - addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType) - addStatement("parser.setInput(inputStream, null)") - addStatement("return parse(parser)") - nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java) - addStatement("return null") - nextControlFlow( - "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType - ) - addStatement("throw new IllegalArgumentException(e)") - } - .build() - -private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> - get() = - mutableListOf<ClassFieldInfo>().apply { - this += this@allClassFields - for (field in fields) { - when (field) { - is ClassFieldInfo -> this += field.allClassFields - is ListFieldInfo -> this += field.element.allClassFields - else -> {} - } - } - } - -private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("parse") - .addAnnotation(nonNullType) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(rootField.type) - .addParameter( - ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() - ) - .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)) - .apply { - addStatement("int type") - addStatement("int depth") - addStatement("int innerDepth = parser.getDepth() + 1") - addControlFlow( - "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" - + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", - xmlPullParserType - ) { - addControlFlow( - "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType - ) { - addStatement("continue") - } - addControlFlow( - "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java, - rootField.tagName - ) { - addStatement("return \$1L(parser)", rootField.parseMethodName) - } - } - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Missing root tag <${rootField.tagName}>" - ) - } - .build() - -private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder(classField.parseMethodName) - .addAnnotation(nonNullType) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(classField.type) - .addParameter( - ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() - ) - .apply { - val (attributeFields, tagFields) = classField.fields - .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } - if (tagFields.isNotEmpty()) { - addExceptions( - listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType) - ) - } - val nameAllocator = NameAllocator().apply { - newName("parser") - newName("type") - newName("depth") - newName("innerDepth") - } - for (field in attributeFields) { - val variableName = nameAllocator.newName(field.variableName, field) - when (field) { - is PrimitiveFieldInfo -> { - val stringVariableName = - nameAllocator.newName("${field.variableName}String") - addStatement( - "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", - stringVariableName, field.attributeName - ) - if (field.isRequired) { - addControlFlow("if (\$1L == null)", stringVariableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Missing attribute \"${field.attributeName}\"" - ) - } - } - val boxedType = field.type.box() - val parseTypeMethodName = if (field.type.isPrimitive) { - "parse${field.type.toString().capitalize()}" - } else { - "valueOf" - } - if (field.isRequired) { - addStatement( - "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName, - boxedType, parseTypeMethodName, stringVariableName - ) - } else { - addStatement( - "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null", - field.type, variableName, stringVariableName, boxedType, - parseTypeMethodName - ) - } - } - is StringFieldInfo -> - addStatement( - "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", - variableName, field.attributeName - ) - else -> error(field) - } - } - if (tagFields.isNotEmpty()) { - for (field in tagFields) { - val variableName = nameAllocator.newName(field.variableName, field) - when (field) { - is ClassFieldInfo -> - addStatement("\$1T \$2L =\$Wnull", field.type, variableName) - is ListFieldInfo -> - addStatement( - "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName, - ArrayList::class.java - ) - else -> error(field) - } - } - addStatement("int type") - addStatement("int depth") - addStatement("int innerDepth = parser.getDepth() + 1") - addControlFlow( - "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" - + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", - xmlPullParserType - ) { - addControlFlow( - "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType - ) { - addStatement("continue") - } - addControlFlow("switch (parser.getName())") { - for (field in tagFields) { - addControlFlow("case \$1S:", field.tagName) { - val variableName = nameAllocator.get(field) - when (field) { - is ClassFieldInfo -> { - addControlFlow("if (\$1L != null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Duplicate tag \"${field.tagName}\"" - ) - } - addStatement( - "\$1L =\$W\$2L(parser)", variableName, - field.parseMethodName - ) - addStatement("break") - } - is ListFieldInfo -> { - val elementNameAllocator = nameAllocator.clone() - val elementVariableName = elementNameAllocator.newName( - field.element.xmlName!!.toLowerCamelCase() - ) - addStatement( - "final \$1T \$2L =\$W\$3L(parser)", field.element.type, - elementVariableName, field.element.parseMethodName - ) - addStatement( - "\$1L.add(\$2L)", variableName, elementVariableName - ) - addStatement("break") - } - else -> error(field) - } - } - } - } - } - } - for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) { - addControlFlow("if ($1L == null)", nameAllocator.get(field)) { - addStatement( - "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>" - ) - } - } - addStatement( - classField.fields.joinToString(",\$W", "return new \$1T(", ")") { - nameAllocator.get(it) - }, classField.type - ) - } - .build() - -private val ClassFieldInfo.parseMethodName: String - get() = "parse${type.simpleName().toUpperCamelCase()}" - -private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer") - -private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("write") - .apply { - val nameAllocator = NameAllocator().apply { - newName("outputStream") - newName("serializer") - } - val parameterName = nameAllocator.newName(rootField.variableName) - addJavadoc( - """ - Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile. - - @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist - """.trimIndent(), rootField.type, parameterName - ) - addAnnotation(nullableType) - addModifiers(Modifier.PUBLIC) - addParameter( - ParameterSpec.builder(rootField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addStatement("\$1T outputStream = null", FileOutputStream::class.java) - addControlFlow("try") { - addStatement("outputStream = mFile.startWrite()") - addStatement( - "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType - ) - addStatement( - "serializer.setOutput(outputStream, \$1T.UTF_8.name())", - StandardCharsets::class.java - ) - addStatement( - "serializer.setFeature(\$1S, true)", - "http://xmlpull.org/v1/doc/features.html#indent-output" - ) - addStatement("serializer.startDocument(null, true)") - addStatement("serialize(serializer,\$W\$1L)", parameterName) - addStatement("serializer.endDocument()") - addStatement("mFile.finishWrite(outputStream)") - nextControlFlow("catch (Exception e)") - addStatement("e.printStackTrace()") - addStatement("mFile.failWrite(outputStream)") - } - } - .build() - -private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("serialize") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .addParameter( - ParameterSpec.builder(xmlSerializerType, "serializer") - .addAnnotation(nonNullType) - .build() - ) - .apply { - val nameAllocator = NameAllocator().apply { newName("serializer") } - val parameterName = nameAllocator.newName(rootField.variableName) - addParameter( - ParameterSpec.builder(rootField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addException(IOException::class.java) - addStatement("serializer.startTag(null, \$1S)", rootField.tagName) - addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName) - addStatement("serializer.endTag(null, \$1S)", rootField.tagName) - } - .build() - -private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder(classField.serializeMethodName) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .addParameter( - ParameterSpec.builder(xmlSerializerType, "serializer") - .addAnnotation(nonNullType) - .build() - ) - .apply { - val nameAllocator = NameAllocator().apply { - newName("serializer") - newName("i") - } - val parameterName = nameAllocator.newName(classField.serializeParameterName) - addParameter( - ParameterSpec.builder(classField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addException(IOException::class.java) - val (attributeFields, tagFields) = classField.fields - .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } - for (field in attributeFields) { - val variableName = "$parameterName.${field.name}" - if (!field.isRequired) { - beginControlFlow("if (\$1L != null)", variableName) - } - when (field) { - is PrimitiveFieldInfo -> { - if (field.isRequired && !field.type.isPrimitive) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - val stringVariableName = - nameAllocator.newName("${field.variableName}String") - addStatement( - "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName, - variableName - ) - addStatement( - "serializer.attribute(null, \$1S, \$2L)", field.attributeName, - stringVariableName - ) - } - is StringFieldInfo -> { - if (field.isRequired) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - addStatement( - "serializer.attribute(null, \$1S, \$2L)", field.attributeName, - variableName - ) - } - else -> error(field) - } - if (!field.isRequired) { - endControlFlow() - } - } - for (field in tagFields) { - val variableName = "$parameterName.${field.name}" - if (field.isRequired) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - when (field) { - is ClassFieldInfo -> { - addStatement("serializer.startTag(null, \$1S)", field.tagName) - addStatement( - "\$1L(serializer, \$2L)", field.serializeMethodName, variableName - ) - addStatement("serializer.endTag(null, \$1S)", field.tagName) - } - is ListFieldInfo -> { - val sizeVariableName = nameAllocator.newName("${field.variableName}Size") - addStatement( - "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName - ) - addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) { - val elementNameAllocator = nameAllocator.clone() - val elementVariableName = elementNameAllocator.newName( - field.element.xmlName!!.toLowerCamelCase() - ) - addStatement( - "final \$1T \$2L =\$W\$3L.get(i)", field.element.type, - elementVariableName, variableName - ) - addControlFlow("if (\$1L == null)", elementVariableName) { - addStatement( - "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)", - "Field element \"${field.name}[", "]\" is null" - ) - } - addStatement("serializer.startTag(null, \$1S)", field.element.tagName) - addStatement( - "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName, - elementVariableName - ) - addStatement("serializer.endTag(null, \$1S)", field.element.tagName) - } - } - else -> error(field) - } - } - } - .build() - -private val ClassFieldInfo.serializeMethodName: String - get() = "serialize${type.simpleName().toUpperCamelCase()}" - -private val ClassFieldInfo.serializeParameterName: String - get() = type.simpleName().toLowerCamelCase() - -private val FieldInfo.variableName: String - get() = name.toLowerCamelCase() - -private val FieldInfo.attributeName: String - get() { - check(this is PrimitiveFieldInfo || this is StringFieldInfo) - return xmlNameOrName.toLowerCamelCase() - } - -private val FieldInfo.tagName: String - get() { - check(this is ClassFieldInfo || this is ListFieldInfo) - return xmlNameOrName.toLowerKebabCase() - } - -private val FieldInfo.xmlNameOrName: String - get() = xmlName ?: name - -private fun generateDeleteMethod(): MethodSpec = - MethodSpec.methodBuilder("delete") - .addJavadoc("Delete the XML file, if any.") - .addModifiers(Modifier.PUBLIC) - .addStatement("mFile.delete()") - .build() - -private inline fun MethodSpec.Builder.addControlFlow( - controlFlow: String, - vararg args: Any, - block: MethodSpec.Builder.() -> Unit -): MethodSpec.Builder { - beginControlFlow(controlFlow, *args) - block() - endControlFlow() - return this -} diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt deleted file mode 100644 index e271f8cb9361..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Main.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -import java.io.File -import java.nio.file.Files - -fun main(args: Array<String>) { - val showUsage = args.isEmpty() || when (args.singleOrNull()) { - "-h", "--help" -> true - else -> false - } - if (showUsage) { - usage() - return - } - - val files = args.flatMap { - File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() } - } - val persistences = parse(files) - for (persistence in persistences) { - val file = generate(persistence) - Files.newBufferedWriter(persistence.path).use { - it.write(FILE_HEADER) - file.writeTo(it) - } - } -} - -private fun usage() { - println("Usage: xmlpersistence <FILES>") -} diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt deleted file mode 100644 index 3ea12a9aa389..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Parser.kt +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -import com.github.javaparser.JavaParser -import com.github.javaparser.ParseProblemException -import com.github.javaparser.ParseResult -import com.github.javaparser.ParserConfiguration -import com.github.javaparser.ast.Node -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration -import com.github.javaparser.ast.body.FieldDeclaration -import com.github.javaparser.ast.body.TypeDeclaration -import com.github.javaparser.ast.expr.AnnotationExpr -import com.github.javaparser.ast.expr.Expression -import com.github.javaparser.ast.expr.NormalAnnotationExpr -import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr -import com.github.javaparser.ast.expr.StringLiteralExpr -import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration -import com.github.javaparser.resolution.types.ResolvedPrimitiveType -import com.github.javaparser.resolution.types.ResolvedReferenceType -import com.github.javaparser.symbolsolver.JavaSymbolSolver -import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration -import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver -import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver -import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import java.nio.file.Path -import java.util.Optional - -class PersistenceInfo( - val name: String, - val root: ClassFieldInfo, - val path: Path -) - -sealed class FieldInfo { - abstract val name: String - abstract val xmlName: String? - abstract val type: TypeName - abstract val isRequired: Boolean -} - -class PrimitiveFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: TypeName, - override val isRequired: Boolean -) : FieldInfo() - -class StringFieldInfo( - override val name: String, - override val xmlName: String?, - override val isRequired: Boolean -) : FieldInfo() { - override val type: TypeName = ClassName.get(String::class.java) -} - -class ClassFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: ClassName, - override val isRequired: Boolean, - val fields: List<FieldInfo> -) : FieldInfo() - -class ListFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: ParameterizedTypeName, - val element: ClassFieldInfo -) : FieldInfo() { - override val isRequired: Boolean = true -} - -fun parse(files: List<Path>): List<PersistenceInfo> { - val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) } - val javaParser = JavaParser(ParserConfiguration() - .setSymbolResolver(JavaSymbolSolver(typeSolver))) - val compilationUnits = files.map { javaParser.parse(it).getOrThrow() } - val memoryTypeSolver = MemoryTypeSolver().apply { - for (compilationUnit in compilationUnits) { - for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) { - val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue - addDeclaration(name, typeDeclaration.resolve()) - } - } - } - typeSolver.add(memoryTypeSolver) - return mutableListOf<PersistenceInfo>().apply { - for (compilationUnit in compilationUnits) { - val classDeclarations = compilationUnit - .getNodesByClass<ClassOrInterfaceDeclaration>() - .filter { !it.isInterface && (!it.isNestedType || it.isStatic) } - this += classDeclarations.mapNotNull { parsePersistenceInfo(it) } - } - } -} - -private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? { - val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull() - ?: return null - val rootClassName = classDeclaration.nameAsString - val name = annotation.getMemberValue("value")?.stringLiteralValue - ?: "${rootClassName}Persistence" - val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull() - ?.getMemberValue("value")?.stringLiteralValue - val root = parseClassFieldInfo( - rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration - ) - val path = classDeclaration.findCompilationUnit().get().storage.get().path - .resolveSibling("$name.java") - return PersistenceInfo(name, root, path) -} - -private fun parseClassFieldInfo( - name: String, - xmlName: String?, - isRequired: Boolean, - classDeclaration: ClassOrInterfaceDeclaration -): ClassFieldInfo { - val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) } - val type = classDeclaration.resolve().typeName - return ClassFieldInfo(name, xmlName, type, isRequired, fields) -} - -private fun parseFieldInfo(field: FieldDeclaration): FieldInfo { - require(field.isPublic && field.isFinal) - val variable = field.variables.single() - val name = variable.nameAsString - val annotations = field.annotations + variable.type.annotations - val annotation = annotations.getByName("XmlName") - val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue - val isRequired = annotations.getByName("NonNull") != null - return when (val type = variable.type.resolve()) { - is ResolvedPrimitiveType -> { - val primitiveType = type.typeName - PrimitiveFieldInfo(name, xmlName, primitiveType, true) - } - is ResolvedReferenceType -> { - when (type.qualifiedName) { - Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name, - Short::class.javaObjectType.name, Char::class.javaObjectType.name, - Integer::class.javaObjectType.name, Long::class.javaObjectType.name, - Float::class.javaObjectType.name, Double::class.javaObjectType.name -> - PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired) - String::class.java.name -> StringFieldInfo(name, xmlName, isRequired) - List::class.java.name -> { - requireNotNull(xmlName) - val elementType = type.typeParametersValues().single() - require(elementType is ResolvedReferenceType) - val listType = ParameterizedTypeName.get( - ClassName.get(List::class.java), elementType.typeName - ) - val element = parseClassFieldInfo( - "(element)", xmlName, true, elementType.classDeclaration - ) - ListFieldInfo(name, xmlName, listType, element) - } - else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration) - } - } - else -> error(type) - } -} - -private fun <T> ParseResult<T>.getOrThrow(): T = - if (isSuccessful) { - result.get() - } else { - throw ParseProblemException(problems) - } - -private inline fun <reified T : Node> Node.getNodesByClass(): List<T> = - getNodesByClass(T::class.java) - -private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply { - if (klass.isInstance(this@getNodesByClass)) { - this += klass.cast(this@getNodesByClass) - } - for (childNode in childNodes) { - this += childNode.getNodesByClass(klass) - } -} - -private fun <T> Optional<T>.getOrNull(): T? = orElse(null) - -private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? = - find { it.name.identifier == name } - -private fun AnnotationExpr.getMemberValue(name: String): Expression? = - when (this) { - is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value - is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null - else -> null - } - -private val Expression.stringLiteralValue: String - get() { - require(this is StringLiteralExpr) - return value - } - -private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration - get() { - val resolvedClassDeclaration = typeDeclaration - require(resolvedClassDeclaration is JavaParserClassDeclaration) - return resolvedClassDeclaration.wrappedNode - } - -private val ResolvedPrimitiveType.typeName: TypeName - get() = - when (this) { - ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN - ResolvedPrimitiveType.BYTE -> TypeName.BYTE - ResolvedPrimitiveType.SHORT -> TypeName.SHORT - ResolvedPrimitiveType.CHAR -> TypeName.CHAR - ResolvedPrimitiveType.INT -> TypeName.INT - ResolvedPrimitiveType.LONG -> TypeName.LONG - ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT - ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE - } - -// This doesn't support type parameters. -private val ResolvedReferenceType.typeName: TypeName - get() = typeDeclaration.typeName - -private val ResolvedReferenceTypeDeclaration.typeName: ClassName - get() { - val packageName = packageName - val classNames = className.split(".") - val topLevelClassName = classNames.first() - val nestedClassNames = classNames.drop(1) - return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray()) - } diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt deleted file mode 100644 index b4bdbba7170b..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -import java.util.Locale - -private val camelHumpBoundary = Regex( - "-" - + "|_" - + "|(?<=[0-9])(?=[^0-9])" - + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])" - + "|(?<=[a-z])(?=[^a-z])" -) - -private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary) - -fun String.toUpperCamelCase(): String = - toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) } - -fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT) - -fun String.toUpperKebabCase(): String = - toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) } - -fun String.toLowerKebabCase(): String = - toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) } - -fun String.toUpperSnakeCase(): String = - toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) } - -fun String.toLowerSnakeCase(): String = - toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) } |