diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 5be21da534a..1a15f000f0c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -2,12 +2,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Xml; -using System.Xml.Linq; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; -using NuGet.Packaging; using ModuleReleaseData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleReleaseData; using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry; @@ -175,13 +172,20 @@ public static TypeMapObjectsXmlFile Import (string filename) if (fi.Length == 0) return unscanned; - var xml = XDocument.Load (filename); - var root = xml.Root ?? throw new InvalidOperationException ($"Invalid XML file '{filename}'"); + using var reader = XmlReader.Create (filename); - var type = root.GetRequiredAttribute ("type"); - var assemblyName = root.GetAttributeOrDefault ("assembly-name", (string?)null); - var mvid = Guid.Parse (root.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())); - var foundJniNativeRegistration = root.GetAttributeOrDefault ("found-jni-native-registration", false); + if (!reader.ReadToFollowing ("api")) + throw new InvalidOperationException ($"Invalid XML file '{filename}'"); + + var type = reader.GetAttribute ("type"); + + if (type.IsNullOrWhiteSpace ()) + throw new InvalidOperationException ($"Missing required attribute 'type' in '{filename}'"); + + var assemblyName = reader.GetAttribute ("assembly-name"); + var mvidValue = reader.GetAttribute ("mvid"); + var mvid = mvidValue.IsNullOrWhiteSpace () ? Guid.Empty : Guid.Parse (mvidValue); + var foundJniNativeRegistration = GetAttributeOrDefault (reader, "found-jni-native-registration", false); var file = new TypeMapObjectsXmlFile { WasScanned = true, @@ -191,59 +195,69 @@ public static TypeMapObjectsXmlFile Import (string filename) }; if (type == "debug") - ImportDebugData (root, file); + ImportDebugData (reader, file); else if (type == "release") - ImportReleaseData (root, file); + ImportReleaseData (reader, file); return file; } - static void ImportDebugData (XElement root, TypeMapObjectsXmlFile file) + static void ImportDebugData (XmlReader reader, TypeMapObjectsXmlFile file) { - var assemblyName = root.GetAttributeOrDefault ("assembly-name", string.Empty); + var assemblyName = file.AssemblyName ?? string.Empty; var isMonoAndroid = assemblyName == "Mono.Android"; - var javaToManaged = root.Element ("java-to-managed"); - - if (javaToManaged is not null) { - foreach (var entry in javaToManaged.Elements ("entry")) - file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); - } - var managedToJava = root.Element ("managed-to-java"); + while (reader.Read ()) { + if (reader.NodeType != XmlNodeType.Element) + continue; - if (managedToJava is not null) { - foreach (var entry in managedToJava.Elements ("entry")) - file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); + if (reader.Name == "java-to-managed") + ReadDebugEntries (reader, file.JavaToManagedDebugEntries, assemblyName, isMonoAndroid); + else if (reader.Name == "managed-to-java") + ReadDebugEntries (reader, file.ManagedToJavaDebugEntries, assemblyName, isMonoAndroid); } } - static void ImportReleaseData (XElement root, TypeMapObjectsXmlFile file) + static void ImportReleaseData (XmlReader reader, TypeMapObjectsXmlFile file) { - var module = root.Element ("module"); - - if (module is null) + if (!reader.ReadToFollowing ("module")) return; + var mvidValue = reader.GetAttribute ("mvid"); file.ModuleReleaseData = new ModuleReleaseData { - AssemblyName = module.GetAttributeOrDefault ("assembly-name", string.Empty), - Mvid = Guid.Parse (module.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())), - MvidBytes = Convert.FromBase64String (module.GetAttributeOrDefault ("mvid-bytes", string.Empty)), + AssemblyName = reader.GetAttribute ("assembly-name") ?? string.Empty, + Mvid = mvidValue.IsNullOrWhiteSpace () ? Guid.Empty : Guid.Parse (mvidValue), + MvidBytes = Convert.FromBase64String (GetAttributeOrDefault (reader, "mvid-bytes", string.Empty)), TypesScratch = new Dictionary (StringComparer.Ordinal), DuplicateTypes = new List (), }; - if (module.Element ("types") is XElement types) - file.ModuleReleaseData.Types = types.Elements ("entry") - .Select (FromReleaseEntryXml) - .ToArray (); - - if (module.Element ("duplicates") is XElement duplicates) - file.ModuleReleaseData.DuplicateTypes.AddRange (duplicates.Elements ("entry") - .Select (FromReleaseEntryXml)); + if (reader.IsEmptyElement) + return; - if (module.Element ("types-scratch") is XElement typesScratch) - file.ModuleReleaseData.TypesScratch.AddRange (typesScratch.Elements ("entry") - .Select (elem => new KeyValuePair (elem.GetAttributeOrDefault ("key", string.Empty), FromReleaseEntryXml (elem)))); + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType != XmlNodeType.Element) + continue; + + switch (reader.Name) { + case "types": + var types = new List (); + ReadReleaseEntries (reader, types); + file.ModuleReleaseData.Types = types.ToArray (); + break; + case "duplicates": + ReadReleaseEntries (reader, file.ModuleReleaseData.DuplicateTypes); + break; + case "types-scratch": + ReadReleaseScratchEntries (reader, file.ModuleReleaseData.TypesScratch); + break; + } + } } public static void WriteEmptyFile (string destination, TaskLoggingHelper log) @@ -254,37 +268,86 @@ public static void WriteEmptyFile (string destination, TaskLoggingHelper log) File.Create (destination).Dispose (); } - static TypeMapDebugEntry FromDebugEntryXml (XElement entry, string assemblyName, bool isMonoAndroid) + static TypeMapDebugEntry FromDebugEntryXml (XmlReader reader, string assemblyName, bool isMonoAndroid) { - var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty); - var managedName = entry.GetAttributeOrDefault ("managed-name", string.Empty); - var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false); - var isInvoker = entry.GetAttributeOrDefault ("is-invoker", false); - var managedTokenId = entry.GetAttributeOrDefault ("managed-type-token-id", (uint)0); - return new TypeMapDebugEntry { - JavaName = javaName, - ManagedName = managedName, - ManagedTypeTokenId = managedTokenId, - SkipInJavaToManaged = skipInJavaToManaged, - IsInvoker = isInvoker, + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedName = reader.GetAttribute ("managed-name") ?? string.Empty, + ManagedTypeTokenId = GetAttributeOrDefault (reader, "managed-type-token-id", 0u), + SkipInJavaToManaged = GetAttributeOrDefault (reader, "skip-in-java-to-managed", false), + IsInvoker = GetAttributeOrDefault (reader, "is-invoker", false), IsMonoAndroid = isMonoAndroid, AssemblyName = assemblyName, }; } - static TypeMapReleaseEntry FromReleaseEntryXml (XElement entry) + static TypeMapReleaseEntry FromReleaseEntryXml (XmlReader reader) { - var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty); - var managedTypeName = entry.GetAttributeOrDefault ("managed-type-name", string.Empty); - var token = entry.GetAttributeOrDefault ("token", 0u); - var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false); - return new TypeMapReleaseEntry { - JavaName = javaName, - ManagedTypeName = managedTypeName, - Token = token, - SkipInJavaToManaged = skipInJavaToManaged, + JavaName = reader.GetAttribute ("java-name") ?? string.Empty, + ManagedTypeName = reader.GetAttribute ("managed-type-name") ?? string.Empty, + Token = GetAttributeOrDefault (reader, "token", 0u), + SkipInJavaToManaged = GetAttributeOrDefault (reader, "skip-in-java-to-managed", false), }; } + + static T GetAttributeOrDefault (XmlReader reader, string name, T defaultValue) + { + var value = reader.GetAttribute (name); + + if (value.IsNullOrWhiteSpace ()) + return defaultValue; + + return (T) Convert.ChangeType (value, typeof (T), CultureInfo.InvariantCulture); + } + + static void ReadDebugEntries (XmlReader reader, List entries, string assemblyName, bool isMonoAndroid) + { + if (reader.IsEmptyElement) + return; + + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") + entries.Add (FromDebugEntryXml (reader, assemblyName, isMonoAndroid)); + } + } + + static void ReadReleaseEntries (XmlReader reader, List entries) + { + if (reader.IsEmptyElement) + return; + + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") + entries.Add (FromReleaseEntryXml (reader)); + } + } + + static void ReadReleaseScratchEntries (XmlReader reader, Dictionary entries) + { + if (reader.IsEmptyElement) + return; + + int depth = reader.Depth; + + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.EndElement && reader.Depth == depth) + return; + + if (reader.NodeType == XmlNodeType.Element && reader.Name == "entry") { + var key = reader.GetAttribute ("key") ?? string.Empty; + entries.Add (key, FromReleaseEntryXml (reader)); + } + } + } }