Dumper.cpp 50 KB

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