void UpdateWhenChanged(string path, string type, MemoryStream ms, Action <Stream> generator)
        {
            if (InstantRunEnabled)
            {
                ms.SetLength(0);
                generator(ms);
                MonoAndroidHelper.CopyIfStreamChanged(ms, path);
            }

            string dataFilePath = $"{path}.inc";

            using (var stream = new NativeAssemblyDataStream()) {
                generator(stream);
                stream.EndOfFile();
                MonoAndroidHelper.CopyIfStreamChanged(stream, dataFilePath);

                var    generatedFiles   = new List <ITaskItem> ();
                string mappingFieldName = $"{type}_typemap";
                string dataFileName     = Path.GetFileName(dataFilePath);
                NativeAssemblerTargetProvider asmTargetProvider;
                var utf8Encoding = new UTF8Encoding(false);
                foreach (string abi in SupportedAbis)
                {
                    ms.SetLength(0);
                    switch (abi.Trim())
                    {
                    case "armeabi-v7a":
                        asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: false);
                        break;

                    case "arm64-v8a":
                        asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: true);
                        break;

                    case "x86":
                        asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: false);
                        break;

                    case "x86_64":
                        asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: true);
                        break;

                    default:
                        throw new InvalidOperationException($"Unknown ABI {abi}");
                    }

                    var    asmgen      = new TypeMappingNativeAssemblyGenerator(asmTargetProvider, stream, dataFileName, stream.MapByteCount, mappingFieldName);
                    string asmFileName = $"{path}.{abi.Trim ()}.s";
                    using (var sw = new StreamWriter(ms, utf8Encoding, bufferSize: 8192, leaveOpen: true)) {
                        asmgen.Write(sw, dataFileName);
                        MonoAndroidHelper.CopyIfStreamChanged(ms, asmFileName);
                    }
                }
            }
        }
        public bool Generate(bool skipJniAddNativeMethodRegistrationAttributeScan, List <TypeDefinition> javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState)
        {
            if (String.IsNullOrEmpty(outputDirectory))
            {
                throw new ArgumentException("must not be null or empty", nameof(outputDirectory));
            }

            if (!Directory.Exists(outputDirectory))
            {
                Directory.CreateDirectory(outputDirectory);
            }

            int assemblyId              = 0;
            int maxJavaNameLength       = 0;
            int maxModuleFileNameLength = 0;
            var knownAssemblies         = new Dictionary <string, int> (StringComparer.Ordinal);
            var tempModules             = new Dictionary <byte[], ModuleData> ();
            Dictionary <AssemblyDefinition, int> moduleCounter = null;
            var mvidCache = new Dictionary <Guid, byte[]> ();

            appConfState = new ApplicationConfigTaskState {
                JniAddNativeMethodRegistrationAttributePresent = skipJniAddNativeMethodRegistrationAttributeScan
            };

            foreach (TypeDefinition td in javaTypes)
            {
                UpdateApplicationConfig(td, appConfState);

                string assemblyName = td.Module.Assembly.FullName;

                if (!knownAssemblies.ContainsKey(assemblyName))
                {
                    assemblyId++;
                    knownAssemblies.Add(assemblyName, assemblyId);
                }

                // We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding
                // byte array representation and on the runtime we need the latter in order to be able to binary search
                // through the module array.
                byte[] moduleUUID;
                if (!mvidCache.TryGetValue(td.Module.Mvid, out moduleUUID))
                {
                    moduleUUID = td.Module.Mvid.ToByteArray();
                    mvidCache.Add(td.Module.Mvid, moduleUUID);
                }

                ModuleData moduleData;
                if (!tempModules.TryGetValue(moduleUUID, out moduleData))
                {
                    if (moduleCounter == null)
                    {
                        moduleCounter = new Dictionary <AssemblyDefinition, int> ();
                    }

                    moduleData = new ModuleData {
                        Mvid           = td.Module.Mvid,
                        MvidBytes      = moduleUUID,
                        Assembly       = td.Module.Assembly,
                        AssemblyName   = td.Module.Assembly.Name.Name,
                        TypesScratch   = new Dictionary <string, TypeMapEntry> (StringComparer.Ordinal),
                        DuplicateTypes = new Dictionary <uint, TypeMapEntry> (),
                    };
                    tempModules.Add(moduleUUID, moduleData);

                    if (!generateNativeAssembly)
                    {
                        int moduleNum;
                        if (!moduleCounter.TryGetValue(moduleData.Assembly, out moduleNum))
                        {
                            moduleNum = 0;
                            moduleCounter [moduleData.Assembly] = 0;
                        }
                        else
                        {
                            moduleNum++;
                            moduleCounter [moduleData.Assembly] = moduleNum;
                        }

                        string fileName = $"{moduleData.Assembly.Name.Name}.{moduleNum}.typemap";
                        moduleData.OutputFilePath = Path.Combine(outputDirectory, fileName);
                        if (maxModuleFileNameLength < fileName.Length)
                        {
                            maxModuleFileNameLength = fileName.Length;
                        }
                    }
                }

                string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(td);
                var    entry    = new TypeMapEntry {
                    JavaName          = javaName,
                    JavaNameLength    = outputEncoding.GetByteCount(javaName),
                    ManagedTypeName   = td.FullName,
                    Token             = td.MetadataToken.ToUInt32(),
                    AssemblyNameIndex = knownAssemblies [assemblyName]
                };

                if (generateNativeAssembly)
                {
                    if (entry.JavaNameLength > maxJavaNameLength)
                    {
                        maxJavaNameLength = entry.JavaNameLength;
                    }
                }

                if (moduleData.TypesScratch.ContainsKey(entry.JavaName))
                {
                    // This is disabled because it costs a lot of time (around 150ms per standard XF Integration app
                    // build) and has no value for the end user. The message is left here because it may be useful to us
                    // in our devloop at some point.
                    //logger ($"Warning: duplicate Java type name '{entry.JavaName}' in assembly '{moduleData.AssemblyName}' (new token: {entry.Token}).");
                    moduleData.DuplicateTypes.Add(entry.Token, entry);
                }
                else
                {
                    moduleData.TypesScratch.Add(entry.JavaName, entry);
                }
            }

            var modules = tempModules.Values.ToArray();

            Array.Sort(modules, new ModuleUUIDArrayComparer());

            var typeMapEntryComparer = new TypeMapEntryArrayComparer();

            foreach (ModuleData module in modules)
            {
                if (module.TypesScratch.Count == 0)
                {
                    module.Types = new TypeMapEntry[0];
                    continue;
                }

                module.Types = module.TypesScratch.Values.ToArray();
                Array.Sort(module.Types, typeMapEntryComparer);
            }

            NativeTypeMappingData data;

            if (!generateNativeAssembly)
            {
                string typeMapIndexPath = Path.Combine(outputDirectory, "typemap.index");
                using (var indexWriter = MemoryStreamPool.Shared.CreateBinaryWriter()) {
                    OutputModules(modules, indexWriter, maxModuleFileNameLength + 1);
                    indexWriter.Flush();
                    MonoAndroidHelper.CopyIfStreamChanged(indexWriter.BaseStream, typeMapIndexPath);
                }
                GeneratedBinaryTypeMaps.Add(typeMapIndexPath);

                data = new NativeTypeMappingData(logger, new ModuleData[0], 0);
            }
            else
            {
                data = new NativeTypeMappingData(logger, modules, maxJavaNameLength + 1);
            }

            NativeAssemblerTargetProvider asmTargetProvider;
            bool sharedBitsWritten = false;
            bool sharedIncludeUsesAbiPrefix;

            foreach (string abi in supportedAbis)
            {
                sharedIncludeUsesAbiPrefix = false;
                switch (abi.Trim())
                {
                case "armeabi-v7a":
                    asmTargetProvider          = new ARMNativeAssemblerTargetProvider(is64Bit: false);
                    sharedIncludeUsesAbiPrefix = true;                             // ARMv7a is "special", it uses different directive prefix
                    // than the others and the "shared" code won't build for it
                    break;

                case "arm64-v8a":
                    asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: true);
                    break;

                case "x86":
                    asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: false);
                    break;

                case "x86_64":
                    asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: true);
                    break;

                default:
                    throw new InvalidOperationException($"Unknown ABI {abi}");
                }

                var generator = new TypeMappingNativeAssemblyGenerator(asmTargetProvider, data, Path.Combine(outputDirectory, "typemaps"), sharedBitsWritten, sharedIncludeUsesAbiPrefix);

                using (var sw = MemoryStreamPool.Shared.CreateStreamWriter(outputEncoding)) {
                    generator.Write(sw);
                    sw.Flush();
                    MonoAndroidHelper.CopyIfStreamChanged(sw.BaseStream, generator.MainSourceFile);
                    if (!sharedIncludeUsesAbiPrefix)
                    {
                        sharedBitsWritten = true;
                    }
                }
            }
            return(true);
        }