Dumper.cpp 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Dumper.h> // Moved to the top because AssetSerializer requires include for the SerializeContext
  9. #include <AzCore/Asset/AssetSerializer.h>
  10. #include <AzCore/Casting/lossy_cast.h>
  11. #include <AzCore/Debug/Trace.h>
  12. #include <AzCore/IO/FileIO.h>
  13. #include <AzCore/IO/GenericStreams.h>
  14. #include <AzCore/IO/Path/Path.h>
  15. #include <AzCore/IO/SystemFile.h>
  16. #include <AzCore/JSON/stringbuffer.h>
  17. #include <AzCore/JSON/pointer.h>
  18. #include <AzCore/JSON/prettywriter.h>
  19. #include <AzCore/Serialization/EditContext.h>
  20. #include <AzCore/Serialization/Json/JsonSerialization.h>
  21. #include <AzCore/Serialization/Json/JsonSerializationSettings.h>
  22. #include <AzCore/Settings/TextParser.h>
  23. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  24. #include <AzCore/std/algorithm.h>
  25. #include <AzCore/std/sort.h>
  26. #include <AzCore/StringFunc/StringFunc.h>
  27. #include <AzCore/Utils/Utils.h>
  28. #include <Application.h>
  29. #include <Utilities.h>
  30. namespace AZ::SerializeContextTools
  31. {
  32. inline namespace Streams
  33. {
  34. // Using an inline namespace for the Function Object Stream
  35. /*
  36. * Implementation of the GenericStream interface
  37. * that uses a function object for writing
  38. */
  39. class FunctorStream
  40. : public AZ::IO::GenericStream
  41. {
  42. public:
  43. using WriteCallback = AZStd::function<AZ::IO::SizeType(AZStd::span<AZStd::byte const>)>;
  44. FunctorStream() = default;
  45. FunctorStream(WriteCallback writeCallback);
  46. bool IsOpen() const override;
  47. bool CanSeek() const override;
  48. bool CanRead() const override;
  49. bool CanWrite() const override;
  50. void Seek(AZ::IO::OffsetType, SeekMode) override;
  51. AZ::IO::SizeType Read(AZ::IO::SizeType, void*) override;
  52. AZ::IO::SizeType Write(AZ::IO::SizeType bytes, const void* iBuffer) override;
  53. AZ::IO::SizeType GetCurPos() const override;
  54. AZ::IO::SizeType GetLength() const override;
  55. AZ::IO::OpenMode GetModeFlags() const override;
  56. const char* GetFilename() const override;
  57. private:
  58. WriteCallback m_streamWriter;
  59. };
  60. FunctorStream::FunctorStream(WriteCallback writeCallback)
  61. : m_streamWriter { AZStd::move(writeCallback)}
  62. {}
  63. bool FunctorStream::IsOpen() const
  64. {
  65. return static_cast<bool>(m_streamWriter);
  66. }
  67. bool FunctorStream::CanSeek() const
  68. {
  69. return false;
  70. }
  71. bool FunctorStream::CanRead() const
  72. {
  73. return false;
  74. }
  75. bool FunctorStream::CanWrite() const
  76. {
  77. return true;
  78. }
  79. void FunctorStream::Seek(AZ::IO::OffsetType, SeekMode)
  80. {
  81. AZ_Assert(false, "Cannot seek in stdout stream");
  82. }
  83. AZ::IO::SizeType FunctorStream::Read(AZ::IO::SizeType, void*)
  84. {
  85. AZ_Assert(false, "The stdout file handle cannot be read from");
  86. return 0;
  87. }
  88. AZ::IO::SizeType FunctorStream::Write(AZ::IO::SizeType bytes, const void* iBuffer)
  89. {
  90. if (m_streamWriter)
  91. {
  92. return m_streamWriter(AZStd::span(reinterpret_cast<const AZStd::byte*>(iBuffer), bytes));
  93. }
  94. return 0;
  95. }
  96. AZ::IO::SizeType FunctorStream::GetCurPos() const
  97. {
  98. return 0;
  99. }
  100. AZ::IO::SizeType FunctorStream::GetLength() const
  101. {
  102. return 0;
  103. }
  104. AZ::IO::OpenMode FunctorStream::GetModeFlags() const
  105. {
  106. return AZ::IO::OpenMode::ModeWrite;
  107. }
  108. const char* FunctorStream::GetFilename() const
  109. {
  110. return "<function object>";
  111. }
  112. }
  113. } // namespace AZ::SerializeContextTools::inline Stream
  114. namespace AZ::SerializeContextTools
  115. {
  116. static auto GetWriteBypassStdoutCapturerFunctor(Application& application)
  117. {
  118. return [&application](AZStd::span<AZStd::byte const> outputBytes)
  119. {
  120. // If the application is currently capturing stdout, use stdout capturer to write
  121. // directly to the file descriptor of stdout
  122. if (AZ::IO::FileDescriptorCapturer* stdoutCapturer = application.GetStdoutCapturer();
  123. stdoutCapturer != nullptr)
  124. {
  125. return stdoutCapturer->WriteBypassingCapture(outputBytes.data(), aznumeric_caster(outputBytes.size()));
  126. }
  127. else
  128. {
  129. constexpr int StdoutDescriptor = 1;
  130. return AZ::IO::PosixInternal::Write(StdoutDescriptor, outputBytes.data(), aznumeric_caster(outputBytes.size()));
  131. }
  132. };
  133. }
  134. bool Dumper::DumpFiles(Application& application)
  135. {
  136. SerializeContext* sc = application.GetSerializeContext();
  137. if (!sc)
  138. {
  139. AZ_Error("SerializeContextTools", false, "No serialize context found.");
  140. return false;
  141. }
  142. AZStd::string outputFolder = Utilities::ReadOutputTargetFromCommandLine(application);
  143. AZ::IO::Path sourceGameFolder;
  144. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  145. {
  146. settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  147. }
  148. bool result = true;
  149. AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
  150. for (const AZStd::string& filePath : fileList)
  151. {
  152. AZ_Printf("DumpFiles", "Dumping file '%.*s'\n", aznumeric_cast<int>(filePath.size()), filePath.data());
  153. AZ::IO::FixedMaxPath outputPath{ AZStd::string_view{ outputFolder }};
  154. outputPath /= AZ::IO::FixedMaxPath(filePath).LexicallyRelative(sourceGameFolder);
  155. outputPath.Native() += ".dump.txt";
  156. IO::SystemFile outputFile;
  157. if (!outputFile.Open(outputPath.c_str(),
  158. IO::SystemFile::OpenMode::SF_OPEN_CREATE |
  159. IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  160. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  161. {
  162. AZ_Error("SerializeContextTools", false, "Unable to open file '%s' for writing.", outputPath.c_str());
  163. result = false;
  164. continue;
  165. }
  166. AZStd::string content;
  167. content.reserve(1 * 1024 * 1024); // Reserve 1mb to avoid frequently resizing the string.
  168. auto callback = [&content, &result](void* classPtr, const Uuid& classId, SerializeContext* context)
  169. {
  170. result = DumpClassContent(content, classPtr, classId, context) && result;
  171. const SerializeContext::ClassData* classData = context->FindClassData(classId);
  172. if (classData && classData->m_factory)
  173. {
  174. classData->m_factory->Destroy(classPtr);
  175. }
  176. else
  177. {
  178. AZ_Error("SerializeContextTools", false, "Missing class factory, so data will leak.");
  179. result = false;
  180. }
  181. };
  182. if (!Utilities::InspectSerializedFile(filePath.c_str(), sc, callback))
  183. {
  184. result = false;
  185. continue;
  186. }
  187. outputFile.Write(content.data(), content.length());
  188. }
  189. return result;
  190. }
  191. bool Dumper::DumpSerializeContext(Application& application)
  192. {
  193. AZStd::string outputPath = Utilities::ReadOutputTargetFromCommandLine(application, "SerializeContext.json");
  194. AZ_Printf("dumpsc", "Writing Serialize Context at '%s'.\n", outputPath.c_str());
  195. IO::SystemFile outputFile;
  196. if (!outputFile.Open(outputPath.c_str(),
  197. IO::SystemFile::OpenMode::SF_OPEN_CREATE |
  198. IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
  199. IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  200. {
  201. AZ_Error("SerializeContextTools", false, "Unable to open output file '%s'.", outputPath.c_str());
  202. return false;
  203. }
  204. SerializeContext* context = application.GetSerializeContext();
  205. AZStd::vector<Uuid> systemComponents = Utilities::GetSystemComponents(application);
  206. AZStd::sort(systemComponents.begin(), systemComponents.end());
  207. rapidjson::Document doc;
  208. rapidjson::Value& root = doc.SetObject();
  209. rapidjson::Value scObject;
  210. scObject.SetObject();
  211. AZStd::string temp;
  212. temp.reserve(256 * 1024); // Reserve 256kb of memory to avoid the string constantly resizing.
  213. bool result = true;
  214. auto callback = [context, &doc, &scObject, &temp, &systemComponents, &result](const SerializeContext::ClassData* classData, const Uuid& /*typeId*/) -> bool
  215. {
  216. if (!DumpClassContent(classData, scObject, doc, systemComponents, context, temp))
  217. {
  218. result = false;
  219. }
  220. return true;
  221. };
  222. context->EnumerateAll(callback, true);
  223. root.AddMember("SerializeContext", AZStd::move(scObject), doc.GetAllocator());
  224. rapidjson::StringBuffer buffer;
  225. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
  226. doc.Accept(writer);
  227. outputFile.Write(buffer.GetString(), buffer.GetSize());
  228. outputFile.Close();
  229. return result;
  230. }
  231. bool Dumper::DumpTypes(Application& application)
  232. {
  233. // outputStream defaults to writing to stdout
  234. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  235. GetWriteBypassStdoutCapturerFunctor(application));
  236. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  237. // If the output-file parameter has been supplied open the file path using FileIOStream
  238. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  239. {
  240. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  241. // If the output file name is a single dash, use the default output stream value which writes to stdout
  242. if (outputPathView != "-")
  243. {
  244. AZ::IO::FixedMaxPath outputPath;
  245. if (outputPathView.IsRelative())
  246. {
  247. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  248. }
  249. else
  250. {
  251. outputPath = outputPathView.LexicallyNormal();
  252. }
  253. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  254. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  255. !fileStream.IsOpen())
  256. {
  257. AZ_Printf(
  258. "dumptypes",
  259. R"(Unable to open specified output-file "%s". Object will not be dumped)"
  260. "\n",
  261. outputPath.c_str());
  262. return false;
  263. }
  264. }
  265. }
  266. AZ::SerializeContext* context = application.GetSerializeContext();
  267. struct TypeNameIdPair
  268. {
  269. AZStd::string m_name;
  270. AZ::TypeId m_id;
  271. bool operator==(const TypeNameIdPair& other) const
  272. {
  273. return m_name == other.m_name && m_id == other.m_id;
  274. }
  275. bool operator!=(const TypeNameIdPair& other) const
  276. {
  277. return !operator==(other);
  278. }
  279. struct Hash
  280. {
  281. size_t operator()(const TypeNameIdPair& key) const
  282. {
  283. size_t hashValue{};
  284. AZStd::hash_combine(hashValue, key.m_name);
  285. AZStd::hash_combine(hashValue, key.m_id);
  286. return hashValue;
  287. }
  288. };
  289. };
  290. // Append the Type names and type ids to a unordered_set to filter out duplicates
  291. AZStd::unordered_set<TypeNameIdPair, TypeNameIdPair::Hash> typeNameIdPairsSet;
  292. auto AppendTypeInfo = [&typeNameIdPairsSet](const SerializeContext::ClassData* classData, const Uuid&) -> bool
  293. {
  294. typeNameIdPairsSet.emplace(TypeNameIdPair{ classData->m_name, classData->m_typeId });
  295. return true;
  296. };
  297. context->EnumerateAll(AppendTypeInfo, true);
  298. // Move over the unordered set over to an array for later
  299. // The array is potentially sorted depending on the sort option
  300. AZStd::vector<TypeNameIdPair> typeNameIdPairs(
  301. AZStd::make_move_iterator(typeNameIdPairsSet.begin()),
  302. AZStd::make_move_iterator(typeNameIdPairsSet.end())
  303. );
  304. // Clear out the Unordered set container
  305. typeNameIdPairsSet = {};
  306. // Sort the TypeNameIdPair based on the --sort option value or by type name if not supplied
  307. enum class SortOptions
  308. {
  309. Name,
  310. TypeId,
  311. None
  312. };
  313. SortOptions sortOption{ SortOptions::Name };
  314. if (size_t sortOptionCount = commandLine.GetNumSwitchValues("sort"); sortOptionCount > 0)
  315. {
  316. AZStd::string sortOptionString = commandLine.GetSwitchValue("sort", sortOptionCount - 1);
  317. if (sortOptionString == "name")
  318. {
  319. sortOption = SortOptions::Name;
  320. }
  321. if (sortOptionString == "typeid")
  322. {
  323. sortOption = SortOptions::TypeId;
  324. }
  325. else if (sortOptionString == "none")
  326. {
  327. sortOption = SortOptions::None;
  328. }
  329. else
  330. {
  331. AZ_Error(
  332. "dumptypes", false,
  333. R"(Invalid --sort option supplied "%s".)"
  334. " Sorting by type name will be used. See --help for valid values)",
  335. sortOptionString.c_str());
  336. }
  337. }
  338. switch (sortOption)
  339. {
  340. case SortOptions::Name:
  341. {
  342. auto SortByName = [](const TypeNameIdPair& lhs, const TypeNameIdPair& rhs)
  343. {
  344. return azstricmp(lhs.m_name.c_str(), rhs.m_name.c_str()) < 0;
  345. };
  346. AZStd::sort(typeNameIdPairs.begin(), typeNameIdPairs.end(), SortByName);
  347. break;
  348. }
  349. case SortOptions::TypeId:
  350. {
  351. auto SortByTypeId = [](const TypeNameIdPair& lhs, const TypeNameIdPair& rhs)
  352. {
  353. return lhs.m_id < rhs.m_id;
  354. };
  355. AZStd::sort(typeNameIdPairs.begin(), typeNameIdPairs.end(), SortByTypeId);
  356. break;
  357. }
  358. case SortOptions::None:
  359. [[fallthrough]];
  360. default:
  361. break;
  362. }
  363. auto GetOutputPathString = [](auto&& stream) -> const char*
  364. {
  365. return stream.GetFilename();
  366. };
  367. AZ_Printf(
  368. "dumptypes",
  369. R"(Writing reflected types to "%s".)"
  370. "\n",
  371. AZStd::visit(GetOutputPathString, outputStream));
  372. auto WriteReflectedTypes = [&typeNameIdPairs](auto&& stream) -> bool
  373. {
  374. AZStd::string typeListContents;
  375. for (auto&& [typeName, typeId] : typeNameIdPairs)
  376. {
  377. typeListContents += AZStd::string::format("%s %s\n", typeName.c_str(), typeId.ToFixedString().c_str());
  378. }
  379. stream.Write(typeListContents.size(), typeListContents.data());
  380. stream.Close();
  381. return true;
  382. };
  383. const bool result = AZStd::visit(WriteReflectedTypes, outputStream);
  384. return result;
  385. }
  386. bool Dumper::CreateType(Application& application)
  387. {
  388. // outputStream defaults to writing to stdout
  389. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  390. GetWriteBypassStdoutCapturerFunctor(application));
  391. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  392. // If the output-file parameter has been supplied open the file path using FileIOStream
  393. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  394. {
  395. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  396. // If the output file name is a single dash, use the default output stream value which writes to stdout
  397. if (outputPathView != "-")
  398. {
  399. AZ::IO::FixedMaxPath outputPath;
  400. if (outputPathView.IsRelative())
  401. {
  402. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  403. }
  404. else
  405. {
  406. outputPath = outputPathView.LexicallyNormal();
  407. }
  408. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  409. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  410. !fileStream.IsOpen())
  411. {
  412. AZ_Printf(
  413. "createtype",
  414. R"(Unable to open specified output-file "%s". Object will not be dumped)"
  415. "\n",
  416. outputPath.c_str());
  417. return false;
  418. }
  419. }
  420. }
  421. size_t typeIdOptionCount = commandLine.GetNumSwitchValues("type-id");
  422. size_t typeNameOptionCount = commandLine.GetNumSwitchValues("type-name");
  423. if (typeIdOptionCount == 0 && typeNameOptionCount == 0)
  424. {
  425. AZ_Error("createtype", false, "One of the following options must be supplied: --type-id or --type-name");
  426. return false;
  427. }
  428. if (typeIdOptionCount > 0 && typeNameOptionCount > 0)
  429. {
  430. AZ_Error("createtype", false, "The --type-id and --type-name options are mutally exclusive. Only one can be specified");
  431. return false;
  432. }
  433. AZ::SerializeContext* context = application.GetSerializeContext();
  434. const AZ::SerializeContext::ClassData* classData = nullptr;
  435. if (typeIdOptionCount > 0)
  436. {
  437. AZStd::string typeIdValue = commandLine.GetSwitchValue("type-id", typeIdOptionCount - 1);
  438. classData = context->FindClassData(AZ::TypeId(typeIdValue.c_str(), typeIdValue.size()));
  439. if (classData == nullptr)
  440. {
  441. AZ_Error("createtype", false, "Type with ID %s is not registered with the SerializeContext", typeIdValue.c_str());
  442. return false;
  443. }
  444. }
  445. else
  446. {
  447. AZStd::string typeNameValue = commandLine.GetSwitchValue("type-name", typeNameOptionCount - 1);
  448. AZStd::vector<AZ::TypeId> classIds = context->FindClassId(AZ::Crc32{ AZStd::string_view{ typeNameValue } });
  449. if (classIds.size() != 1)
  450. {
  451. if (classIds.empty())
  452. {
  453. AZ_Error("createtype", false, "Type with name %s is not registered with the SerializeContext", typeNameValue.c_str());
  454. }
  455. else
  456. {
  457. const char* prependComma = "";
  458. AZStd::string classIdString;
  459. for (const AZ::TypeId& classId : classIds)
  460. {
  461. classIdString += prependComma + classId.ToString<AZStd::string>();
  462. prependComma = ", ";
  463. }
  464. AZ_Error(
  465. "createtype", classIds.size() < 2,
  466. "Multiple types with name %s have been registered with the SerializeContext,\n"
  467. "In order to disambiguate which type to use, the --type-id argument must be supplied with one of the following "
  468. "Uuids:\n",
  469. "%s", typeNameValue.c_str(), classIdString.c_str());
  470. }
  471. return false;
  472. }
  473. // Only one class with this typename has been registered with the serialize context, so look up its ClassData
  474. classData = context->FindClassData(classIds.front());
  475. }
  476. // Create a rapidjson document to store the default constructed object
  477. const AZStd::any typeInst = context->CreateAny(classData->m_typeId);
  478. rapidjson::Document document;
  479. rapidjson::Value& root = document.SetObject();
  480. AZ::JsonSerializerSettings serializerSettings;
  481. serializerSettings.m_serializeContext = context;
  482. serializerSettings.m_registrationContext = application.GetJsonRegistrationContext();
  483. serializerSettings.m_keepDefaults = true;
  484. using JsonResultCode = AZ::JsonSerializationResult::ResultCode;
  485. const JsonResultCode parseResult = AZ::JsonSerialization::Store(
  486. root, document.GetAllocator(), AZStd::any_cast<void>(&typeInst), nullptr, typeInst.type(), serializerSettings);
  487. if (parseResult.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  488. {
  489. AZ_Printf("createtype", " Failed to store type %s in JSON format.\n", classData->m_name);
  490. return false;
  491. }
  492. auto GetOutputPathString = [](auto&& stream)
  493. {
  494. using StreamType = AZStd::remove_cvref_t<decltype(stream)>;
  495. if constexpr (AZStd::is_same_v<StreamType, AZ::IO::StdoutStream>)
  496. {
  497. return AZ::IO::FixedMaxPath{ "<stdout>" };
  498. }
  499. else if (AZStd::is_same_v<StreamType, AZ::IO::FileIOStream>)
  500. {
  501. return AZ::IO::FixedMaxPath{ stream.GetFilename() };
  502. }
  503. else
  504. {
  505. AZ_Assert(false, "OutputStream has invalid stream type. It must be StdoutStream or FileIOStream");
  506. return AZ::IO::FixedMaxPath{};
  507. }
  508. };
  509. AZ_Printf(
  510. "createtype",
  511. R"(Writing Type "%s" to "%s" using Json Serialization.)"
  512. "\n",
  513. classData->m_name, AZStd::visit(GetOutputPathString, outputStream).c_str());
  514. AZStd::string jsonDocumentRootPrefix;
  515. if (commandLine.HasSwitch("json-prefix"))
  516. {
  517. jsonDocumentRootPrefix = commandLine.GetSwitchValue("json-prefix", 0);
  518. }
  519. auto VisitStream = [&document, &jsonDocumentRootPrefix](auto&& stream) -> bool
  520. {
  521. if (WriteDocumentToStream(stream, document, jsonDocumentRootPrefix))
  522. {
  523. // Write out a newline to the end of the stream
  524. constexpr AZStd::string_view newline = "\n";
  525. stream.Write(newline.size(), newline.data());
  526. return true;
  527. }
  528. return false;
  529. };
  530. const bool result = AZStd::visit(VisitStream, outputStream);
  531. return result;
  532. }
  533. bool Dumper::CreateUuid(Application& application)
  534. {
  535. // outputStream defaults to writing to stdout
  536. AZStd::variant<FunctorStream, AZ::IO::SystemFileStream> outputStream(AZStd::in_place_type<FunctorStream>,
  537. GetWriteBypassStdoutCapturerFunctor(application));
  538. AZ::CommandLine& commandLine = *application.GetAzCommandLine();
  539. // If the output-file parameter has been supplied open the file path using FileIOStream
  540. if (size_t optionCount = commandLine.GetNumSwitchValues("output-file"); optionCount > 0)
  541. {
  542. AZ::IO::PathView outputPathView(commandLine.GetSwitchValue("output-file", optionCount - 1));
  543. // If the output file name is a single dash, use the default output stream value which writes to stdout
  544. if (outputPathView != "-")
  545. {
  546. AZ::IO::FixedMaxPath outputPath;
  547. if (outputPathView.IsRelative())
  548. {
  549. AZ::Utils::ConvertToAbsolutePath(outputPath, outputPathView.Native());
  550. }
  551. else
  552. {
  553. outputPath = outputPathView.LexicallyNormal();
  554. }
  555. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  556. if (auto& fileStream = outputStream.emplace<AZ::IO::SystemFileStream>(outputPath.c_str(), openMode);
  557. !fileStream.IsOpen())
  558. {
  559. AZ_Printf(
  560. "createuuid",
  561. R"(Unable to open specified output-file "%s". Uuid will not be output to stream)"
  562. "\n",
  563. outputPath.c_str());
  564. return false;
  565. }
  566. }
  567. }
  568. size_t valuesOptionCount = commandLine.GetNumSwitchValues("values");
  569. size_t valuesFileOptionCount = commandLine.GetNumSwitchValues("values-file");
  570. if (valuesOptionCount == 0 && valuesFileOptionCount == 0)
  571. {
  572. AZ_Error("createuuid", false, "One of following options must be supplied: --values or --values-file");
  573. return false;
  574. }
  575. bool withCurlyBraces = true;
  576. if (size_t withCurlyBracesOptionCount = commandLine.GetNumSwitchValues("with-curly-braces");
  577. withCurlyBracesOptionCount > 0)
  578. {
  579. withCurlyBraces = AZ::StringFunc::ToBool(commandLine.GetSwitchValue("with-curly-braces", withCurlyBracesOptionCount - 1).c_str());
  580. }
  581. bool withDashes = true;
  582. if (size_t withDashesOptionCount = commandLine.GetNumSwitchValues("with-dashes");
  583. withDashesOptionCount > 0)
  584. {
  585. withDashes = AZ::StringFunc::ToBool(commandLine.GetSwitchValue("with-dashes", withDashesOptionCount - 1).c_str());
  586. }
  587. const bool quietOutput = commandLine.HasSwitch("q") || commandLine.HasSwitch("quiet");
  588. bool result = true;
  589. struct UuidStringPair
  590. {
  591. AZ::Uuid m_uuid;
  592. AZStd::string m_value;
  593. };
  594. AZStd::vector<UuidStringPair> uuidsToWrite;
  595. for (size_t i = 0; i < valuesOptionCount; ++i)
  596. {
  597. AZStd::string value = commandLine.GetSwitchValue("values", i);
  598. auto uuidFromName = AZ::Uuid::CreateName(value);
  599. uuidsToWrite.push_back({ AZStd::move(uuidFromName), AZStd::move(value) });
  600. }
  601. // Read string values from each --values-file argument
  602. for (size_t i = 0; i < valuesFileOptionCount; ++i)
  603. {
  604. AZ::IO::FixedMaxPath inputValuePath(AZ::IO::PathView(commandLine.GetSwitchValue("values-file", i)));
  605. AZ::IO::SystemFileStream valuesFileStream;
  606. if (inputValuePath == "-")
  607. {
  608. // If the input file is dash read from stdin
  609. valuesFileStream = AZ::IO::SystemFileStream(AZ::IO::SystemFile::GetStdin());
  610. }
  611. else
  612. {
  613. // Open the path from the values-file option
  614. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeRead;
  615. valuesFileStream.Open(inputValuePath.c_str(), openMode);
  616. }
  617. if (valuesFileStream.IsOpen())
  618. {
  619. // Use the text parser to parse plain text lines
  620. AZ::Settings::TextParserSettings textParserSettings;
  621. textParserSettings.m_parseTextEntryFunc = [&uuidsToWrite](AZStd::string_view token)
  622. {
  623. // Remove leading and surrounding spaces and carriage returns
  624. token = AZ::StringFunc::StripEnds(token, " \r");
  625. auto uuidFromName = AZ::Uuid::CreateName(token);
  626. uuidsToWrite.push_back({ AZStd::move(uuidFromName), token });
  627. return true;
  628. };
  629. AZ::Settings::ParseTextFile(valuesFileStream, textParserSettings);
  630. }
  631. }
  632. for (const UuidStringPair& uuidStringPair : uuidsToWrite)
  633. {
  634. auto VisitStream = [&uuidToWrite = uuidStringPair.m_uuid, &value = uuidStringPair.m_value,
  635. withCurlyBraces, withDashes, quietOutput](auto&& stream) -> bool
  636. {
  637. AZStd::fixed_string<256> uuidString;
  638. if (quietOutput)
  639. {
  640. uuidString = AZStd::fixed_string<256>::format("%s\n",
  641. uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str());
  642. }
  643. else
  644. {
  645. uuidString = AZStd::fixed_string<256>::format(R"(%s %s)" "\n",
  646. uuidToWrite.ToFixedString(withCurlyBraces, withDashes).c_str(),
  647. value.c_str());
  648. }
  649. size_t bytesWritten = stream.Write(uuidString.size(), uuidString.c_str());
  650. return bytesWritten == uuidString.size();
  651. };
  652. result = AZStd::visit(VisitStream, outputStream) && result;
  653. }
  654. return result;
  655. }
  656. AZStd::vector<Uuid> Dumper::CreateFilterListByNames(SerializeContext* context, AZStd::string_view name)
  657. {
  658. AZStd::vector<AZStd::string_view> names;
  659. auto AppendNames = [&names](AZStd::string_view filename)
  660. {
  661. names.emplace_back(filename);
  662. };
  663. AZ::StringFunc::TokenizeVisitor(name, AppendNames, ';');
  664. AZStd::vector<Uuid> filterIds;
  665. filterIds.reserve(names.size());
  666. for (const AZStd::string_view& singleName : names)
  667. {
  668. AZStd::vector<Uuid> foundFilters = context->FindClassId(Crc32(singleName.data(), singleName.length(), true));
  669. filterIds.insert(filterIds.end(), foundFilters.begin(), foundFilters.end());
  670. }
  671. return filterIds;
  672. }
  673. AZStd::string_view Dumper::ExtractNamespace(const AZStd::string& name)
  674. {
  675. size_t offset = 0;
  676. const char* startChar = name.data();
  677. const char* currentChar = name.data();
  678. while (*currentChar != 0 && *currentChar != '<')
  679. {
  680. if (*currentChar != ':')
  681. {
  682. ++currentChar;
  683. }
  684. else
  685. {
  686. ++currentChar;
  687. if (*currentChar == ':')
  688. {
  689. AZ_Assert(currentChar - startChar >= 1, "Offset out of bounds while trying to extract namespace from name '%s'.", name.c_str());
  690. offset = currentChar - startChar - 1; // -1 to exclude the last "::"
  691. }
  692. }
  693. }
  694. return AZStd::string_view(startChar, offset);
  695. }
  696. rapidjson::Value Dumper::WriteToJsonValue(const Uuid& uuid, rapidjson::Document& document)
  697. {
  698. char buffer[Uuid::MaxStringBuffer];
  699. int writtenCount = uuid.ToString(buffer, AZ_ARRAY_SIZE(buffer));
  700. if (writtenCount > 0)
  701. {
  702. return rapidjson::Value(buffer, writtenCount - 1, document.GetAllocator()); //-1 as the null character shouldn't be written.
  703. }
  704. else
  705. {
  706. return rapidjson::Value(rapidjson::StringRef("{uuid conversion failed}"));
  707. }
  708. }
  709. bool Dumper::DumpClassContent(const SerializeContext::ClassData* classData, rapidjson::Value& parent, rapidjson::Document& document,
  710. const AZStd::vector<Uuid>& systemComponents, SerializeContext* context, AZStd::string& scratchStringBuffer)
  711. {
  712. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
  713. rapidjson::Value classNode(rapidjson::kObjectType);
  714. DumpClassName(classNode, context, classData, document, scratchStringBuffer);
  715. Edit::ClassData* editData = classData->m_editData;
  716. GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
  717. if (editData && editData->m_description)
  718. {
  719. AZStd::string_view description = editData->m_description;
  720. // Skipping if there's only one character as there are several cases where a blank description is given.
  721. if (description.size() > 1)
  722. {
  723. classNode.AddMember("Description", rapidjson::Value(description.data(), document.GetAllocator()), document.GetAllocator());
  724. }
  725. }
  726. classNode.AddMember("Id", rapidjson::StringRef(classData->m_name), document.GetAllocator());
  727. classNode.AddMember("Version", classData->IsDeprecated() ?
  728. rapidjson::Value(rapidjson::StringRef("Deprecated")) : rapidjson::Value(classData->m_version), document.GetAllocator());
  729. auto systemComponentIt = AZStd::lower_bound(systemComponents.begin(), systemComponents.end(), classData->m_typeId);
  730. const bool isSystemComponent = systemComponentIt != systemComponents.end() && *systemComponentIt == classData->m_typeId;
  731. classNode.AddMember("IsSystemComponent", isSystemComponent, document.GetAllocator());
  732. const bool isComponent = isSystemComponent || (classData->m_azRtti != nullptr && classData->m_azRtti->IsTypeOf<AZ::Component>());
  733. classNode.AddMember("IsComponent", isComponent, document.GetAllocator());
  734. classNode.AddMember("IsPrimitive", Utilities::IsSerializationPrimitive(genericClassInfo ? genericClassInfo->GetGenericTypeId() : classData->m_typeId), document.GetAllocator());
  735. classNode.AddMember("IsContainer", classData->m_container != nullptr, document.GetAllocator());
  736. if (genericClassInfo)
  737. {
  738. classNode.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
  739. classNode.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
  740. }
  741. if (!classData->m_elements.empty())
  742. {
  743. rapidjson::Value fields(rapidjson::kArrayType);
  744. rapidjson::Value bases(rapidjson::kArrayType);
  745. for (const SerializeContext::ClassElement& element : classData->m_elements)
  746. {
  747. DumpElementInfo(element, classData, context, fields, bases, document, scratchStringBuffer);
  748. }
  749. if (!bases.Empty())
  750. {
  751. classNode.AddMember("Bases", AZStd::move(bases), document.GetAllocator());
  752. }
  753. if (!fields.Empty())
  754. {
  755. classNode.AddMember("Fields", AZStd::move(fields), document.GetAllocator());
  756. }
  757. }
  758. parent.AddMember(WriteToJsonValue(classData->m_typeId, document), AZStd::move(classNode), document.GetAllocator());
  759. return true;
  760. }
  761. bool Dumper::DumpClassContent(AZStd::string& output, void* classPtr, const Uuid& classId, SerializeContext* context)
  762. {
  763. const SerializeContext::ClassData* classData = context->FindClassData(classId);
  764. if (!classData)
  765. {
  766. AZ_Printf("", " Class data for '%s' is missing.\n", classId.ToString<AZStd::string>().c_str());
  767. return false;
  768. }
  769. size_t indention = 0;
  770. auto begin = [context, &output, &indention](void* /*instance*/, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement) -> bool
  771. {
  772. for (size_t i = 0; i < indention; ++i)
  773. {
  774. output += ' ';
  775. }
  776. if (classData)
  777. {
  778. output += classData->m_name;
  779. }
  780. DumpElementInfo(output, classElement, context);
  781. DumpPrimitiveTag(output, classData, classElement);
  782. output += '\n';
  783. indention += 2;
  784. return true;
  785. };
  786. auto end = [&indention]() -> bool
  787. {
  788. indention = indention > 0 ? indention - 2 : 0;
  789. return true;
  790. };
  791. SerializeContext::EnumerateInstanceCallContext callContext(begin, end, context, SerializeContext::ENUM_ACCESS_FOR_WRITE, nullptr);
  792. context->EnumerateInstance(&callContext, classPtr, classId, classData, nullptr);
  793. return true;
  794. }
  795. void Dumper::DumpElementInfo(const SerializeContext::ClassElement& element, const SerializeContext::ClassData* classData, SerializeContext* context,
  796. rapidjson::Value& fields, rapidjson::Value& bases, rapidjson::Document& document, AZStd::string& scratchStringBuffer)
  797. {
  798. AZ_Assert(fields.IsArray(), "Expected 'fields' to be an array.");
  799. AZ_Assert(bases.IsArray(), "Expected 'bases' to be an array.");
  800. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
  801. const SerializeContext::ClassData* elementClass = context->FindClassData(element.m_typeId, classData);
  802. AppendTypeName(scratchStringBuffer, elementClass, element.m_typeId);
  803. Uuid elementTypeId = element.m_typeId;
  804. if (element.m_genericClassInfo)
  805. {
  806. DumpGenericStructure(scratchStringBuffer, element.m_genericClassInfo, context);
  807. elementTypeId = element.m_genericClassInfo->GetSpecializedTypeId();
  808. }
  809. if ((element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  810. {
  811. scratchStringBuffer += '*';
  812. }
  813. rapidjson::Value elementTypeString(scratchStringBuffer.c_str(), document.GetAllocator());
  814. scratchStringBuffer.clear();
  815. if ((element.m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
  816. {
  817. rapidjson::Value baseNode(rapidjson::kObjectType);
  818. baseNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
  819. baseNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
  820. bases.PushBack(AZStd::move(baseNode), document.GetAllocator());
  821. }
  822. else
  823. {
  824. rapidjson::Value elementNode(rapidjson::kObjectType);
  825. elementNode.AddMember("Name", rapidjson::StringRef(element.m_name), document.GetAllocator());
  826. elementNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
  827. elementNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
  828. elementNode.AddMember("HasDefault", (element.m_flags & SerializeContext::ClassElement::FLG_NO_DEFAULT_VALUE) == 0, document.GetAllocator());
  829. elementNode.AddMember("IsDynamic", (element.m_flags & SerializeContext::ClassElement::FLG_DYNAMIC_FIELD) != 0, document.GetAllocator());
  830. elementNode.AddMember("IsPointer", (element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, document.GetAllocator());
  831. elementNode.AddMember("IsUiElement", (element.m_flags & SerializeContext::ClassElement::FLG_UI_ELEMENT) != 0, document.GetAllocator());
  832. elementNode.AddMember("DataSize", static_cast<uint64_t>(element.m_dataSize), document.GetAllocator());
  833. elementNode.AddMember("Offset", static_cast<uint64_t>(element.m_offset), document.GetAllocator());
  834. Edit::ElementData* elementEditData = element.m_editData;
  835. if (elementEditData)
  836. {
  837. elementNode.AddMember("Description", rapidjson::StringRef(elementEditData->m_description), document.GetAllocator());
  838. }
  839. if (element.m_genericClassInfo)
  840. {
  841. rapidjson::Value genericArray(rapidjson::kArrayType);
  842. rapidjson::Value classObject(rapidjson::kObjectType);
  843. const SerializeContext::ClassData* genericClassData = element.m_genericClassInfo->GetClassData();
  844. classObject.AddMember("Type", rapidjson::StringRef(genericClassData->m_name), document.GetAllocator());
  845. classObject.AddMember("GenericUuid", WriteToJsonValue(element.m_genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
  846. classObject.AddMember("SpecializedUuid", WriteToJsonValue(element.m_genericClassInfo->GetSpecializedTypeId(), document), document.GetAllocator());
  847. classObject.AddMember("Generics", DumpGenericStructure(element.m_genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
  848. genericArray.PushBack(AZStd::move(classObject), document.GetAllocator());
  849. elementNode.AddMember("Generics", AZStd::move(genericArray), document.GetAllocator());
  850. }
  851. fields.PushBack(AZStd::move(elementNode), document.GetAllocator());
  852. }
  853. }
  854. void Dumper::DumpElementInfo(AZStd::string& output, const SerializeContext::ClassElement* classElement, SerializeContext* context)
  855. {
  856. if (classElement)
  857. {
  858. if (classElement->m_genericClassInfo)
  859. {
  860. DumpGenericStructure(output, classElement->m_genericClassInfo, context);
  861. }
  862. if ((classElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  863. {
  864. output += '*';
  865. }
  866. output += ' ';
  867. output += classElement->m_name;
  868. if ((classElement->m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
  869. {
  870. output += " [Base]";
  871. }
  872. }
  873. }
  874. void Dumper::DumpGenericStructure(AZStd::string& output, GenericClassInfo* genericClassInfo, SerializeContext* context)
  875. {
  876. output += '<';
  877. const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
  878. if (classData && classData->m_container)
  879. {
  880. bool firstArgument = true;
  881. auto callback = [&output, context, &firstArgument](const Uuid& elementClassId, const SerializeContext::ClassElement* genericClassElement) -> bool
  882. {
  883. if (!firstArgument)
  884. {
  885. output += ',';
  886. }
  887. else
  888. {
  889. firstArgument = false;
  890. }
  891. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  892. AppendTypeName(output, argClassData, elementClassId);
  893. if (genericClassElement->m_genericClassInfo)
  894. {
  895. DumpGenericStructure(output, genericClassElement->m_genericClassInfo, context);
  896. }
  897. if ((genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
  898. {
  899. output += '*';
  900. }
  901. return true;
  902. };
  903. classData->m_container->EnumTypes(callback);
  904. }
  905. else
  906. {
  907. // No container information available, so as much as possible through other means, although
  908. // this might not be complete information.
  909. size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
  910. for (size_t i = 0; i < numArgs; ++i)
  911. {
  912. if (i != 0)
  913. {
  914. output += ',';
  915. }
  916. const Uuid& argClassId = genericClassInfo->GetTemplatedTypeId(i);
  917. const SerializeContext::ClassData* argClass = context->FindClassData(argClassId);
  918. AppendTypeName(output, argClass, argClassId);
  919. }
  920. }
  921. output += '>';
  922. }
  923. rapidjson::Value Dumper::DumpGenericStructure(GenericClassInfo* genericClassInfo, SerializeContext* context,
  924. rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
  925. {
  926. AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer still contains data.");
  927. rapidjson::Value result(rapidjson::kArrayType);
  928. const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
  929. if (classData && classData->m_container)
  930. {
  931. auto callback = [&result, context, &parentDoc, &scratchStringBuffer](const Uuid& elementClassId,
  932. const SerializeContext::ClassElement* genericClassElement) -> bool
  933. {
  934. rapidjson::Value classObject(rapidjson::kObjectType);
  935. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  936. AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
  937. classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  938. scratchStringBuffer.clear();
  939. classObject.AddMember("IsPointer", (genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, parentDoc.GetAllocator());
  940. if (genericClassElement->m_genericClassInfo)
  941. {
  942. GenericClassInfo* genericClassInfo = genericClassElement->m_genericClassInfo;
  943. classObject.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), parentDoc), parentDoc.GetAllocator());
  944. classObject.AddMember("SpecializedUuid", WriteToJsonValue(genericClassInfo->GetSpecializedTypeId(), parentDoc), parentDoc.GetAllocator());
  945. classObject.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, parentDoc, scratchStringBuffer), parentDoc.GetAllocator());
  946. }
  947. else
  948. {
  949. classObject.AddMember("GenericUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  950. classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  951. }
  952. result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
  953. return true;
  954. };
  955. classData->m_container->EnumTypes(callback);
  956. }
  957. else
  958. {
  959. // No container information available, so as much as possible through other means, although
  960. // this might not be complete information.
  961. size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
  962. for (size_t i = 0; i < numArgs; ++i)
  963. {
  964. const Uuid& elementClassId = genericClassInfo->GetTemplatedTypeId(i);
  965. rapidjson::Value classObject(rapidjson::kObjectType);
  966. const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
  967. AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
  968. classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  969. scratchStringBuffer.clear();
  970. classObject.AddMember("GenericUuid",
  971. WriteToJsonValue(argClassData ? argClassData->m_typeId : elementClassId, parentDoc), parentDoc.GetAllocator());
  972. classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
  973. classObject.AddMember("IsPointer", false, parentDoc.GetAllocator());
  974. result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
  975. }
  976. }
  977. return result;
  978. }
  979. void Dumper::DumpPrimitiveTag(AZStd::string& output, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)
  980. {
  981. if (classData)
  982. {
  983. Uuid classId = classData->m_typeId;
  984. if (classElement && classElement->m_genericClassInfo)
  985. {
  986. classId = classElement->m_genericClassInfo->GetGenericTypeId();
  987. }
  988. if (Utilities::IsSerializationPrimitive(classId))
  989. {
  990. output += " [Primitive]";
  991. }
  992. }
  993. }
  994. void Dumper::DumpClassName(rapidjson::Value& parent, SerializeContext* context, const SerializeContext::ClassData* classData,
  995. rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
  996. {
  997. AZ_Assert(scratchStringBuffer.empty(), "Scratch string buffer is not empty.");
  998. Edit::ClassData* editData = classData->m_editData;
  999. GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
  1000. if (genericClassInfo)
  1001. {
  1002. // If the type itself is a generic, dump it's information.
  1003. scratchStringBuffer = classData->m_name;
  1004. DumpGenericStructure(scratchStringBuffer, genericClassInfo, context);
  1005. }
  1006. else
  1007. {
  1008. bool hasEditName = editData && editData->m_name && strlen(editData->m_name) > 0;
  1009. scratchStringBuffer = hasEditName ? editData->m_name : classData->m_name;
  1010. }
  1011. AZStd::string_view namespacePortion = ExtractNamespace(scratchStringBuffer);
  1012. if (!namespacePortion.empty())
  1013. {
  1014. parent.AddMember("Namespace",
  1015. rapidjson::Value(namespacePortion.data(), azlossy_caster(namespacePortion.length()), parentDoc.GetAllocator()),
  1016. parentDoc.GetAllocator());
  1017. parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str() + namespacePortion.length() + 2, parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1018. }
  1019. else
  1020. {
  1021. parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
  1022. }
  1023. scratchStringBuffer.clear();
  1024. }
  1025. void Dumper::AppendTypeName(AZStd::string& output, const SerializeContext::ClassData* classData, const Uuid& classId)
  1026. {
  1027. if (classData)
  1028. {
  1029. output += classData->m_name;
  1030. }
  1031. else if (classId == GetAssetClassId())
  1032. {
  1033. output += "Asset";
  1034. }
  1035. else
  1036. {
  1037. output += classId.ToString<AZStd::string>();
  1038. }
  1039. }
  1040. bool Dumper::WriteDocumentToStream(AZ::IO::GenericStream& outputStream, const rapidjson::Document& document,
  1041. AZStd::string_view pointerRoot)
  1042. {
  1043. rapidjson::StringBuffer scratchBuffer;
  1044. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(scratchBuffer);
  1045. // rapidjson::Pointer constructor attempts to dereference the const char* index 0 even if the size is 0
  1046. // so make sure the string_view isn't referencing a nullptr
  1047. rapidjson::Pointer jsonPointerAnchor(pointerRoot.data() ? pointerRoot.data() : "", pointerRoot.size());
  1048. // Anchor the content in the Json Document under the Json Pointer root path
  1049. rapidjson::Document rootDocument;
  1050. rapidjson::SetValueByPointer(rootDocument, jsonPointerAnchor, document);
  1051. rootDocument.Accept(writer);
  1052. outputStream.Write(scratchBuffer.GetSize(), scratchBuffer.GetString());
  1053. scratchBuffer.Clear();
  1054. return true;
  1055. }
  1056. // namespace AZ::SerializeContextTools
  1057. }