public override bool RunTask()
        {
            var resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap(ResourceNameCaseMap);
            var acw_map       = MonoAndroidHelper.LoadAcwMapFile(AcwMapFile);
            var customViewMap = MonoAndroidHelper.LoadCustomViewMapFile(BuildEngine4, CustomViewMapFile);
            var processed     = new HashSet <string> ();

            foreach (var kvp in acw_map)
            {
                var key   = kvp.Key;
                var value = kvp.Value;
                if (key == value)
                {
                    continue;
                }
                if (customViewMap.TryGetValue(key, out HashSet <string> resourceFiles))
                {
                    foreach (var file in resourceFiles)
                    {
                        if (processed.Contains(file))
                        {
                            continue;
                        }
                        if (!File.Exists(file))
                        {
                            continue;
                        }
                        var  document = XDocument.Load(file, options: LoadOptions.SetLineInfo);
                        var  e        = document.Root;
                        bool update   = false;
                        foreach (var elem in AndroidResource.GetElements(e).Prepend(e))
                        {
                            update |= TryFixCustomView(elem, acw_map, (level, message) => {
                                ITaskItem resdir = ResourceDirectories?.FirstOrDefault(x => file.StartsWith(x.ItemSpec, StringComparison.OrdinalIgnoreCase)) ?? null;
                                switch (level)
                                {
                                case TraceLevel.Error:
                                    Log.FixupResourceFilenameAndLogCodedError("XA1002", message, file, resdir?.ItemSpec, resource_name_case_map);
                                    break;

                                case TraceLevel.Warning:
                                    Log.FixupResourceFilenameAndLogCodedError("XA1001", message, file, resdir?.ItemSpec, resource_name_case_map);
                                    break;

                                default:
                                    Log.LogDebugMessage(message);
                                    break;
                                }
                            });
                        }
                        foreach (XAttribute a in AndroidResource.GetAttributes(e))
                        {
                            update |= TryFixCustomClassAttribute(a, acw_map);
                            update |= TryFixFragment(a, acw_map);
                        }
                        if (update)
                        {
                            var lastModified = File.GetLastWriteTimeUtc(file);
                            if (document.SaveIfChanged(file))
                            {
                                Log.LogDebugMessage($"Fixed up Custom Views in {file}");
                                File.SetLastWriteTimeUtc(file, lastModified);
                            }
                        }
                        processed.Add(file);
                    }
                }
            }
            var output = new Dictionary <string, ITaskItem> (processed.Count);

            foreach (var file in processed)
            {
                ITaskItem resdir    = ResourceDirectories?.FirstOrDefault(x => file.StartsWith(x.ItemSpec)) ?? null;
                var       hash      = resdir?.GetMetadata("Hash") ?? null;
                var       stamp     = resdir?.GetMetadata("StampFile") ?? null;
                var       filename  = !string.IsNullOrEmpty(hash) ? hash : "compiled";
                var       stampFile = !string.IsNullOrEmpty(stamp) ? stamp : $"{filename}.stamp";
                Log.LogDebugMessage($"{filename} {stampFile}");
                output.Add(file, new TaskItem(Path.GetFullPath(file), new Dictionary <string, string> {
                    { "StampFile", stampFile },
                    { "Hash", filename },
                    { "Resource", resdir?.ItemSpec ?? file },
                }));
            }
            Processed = output.Values.ToArray();
            return(!Log.HasLoggedErrors);
        }
        public override bool Execute()
        {
            if (SourceFiles.Length != DestinationFiles.Length)
            {
                throw new ArgumentException("source and destination count mismatch");
            }

            var acw_map = MonoAndroidHelper.LoadAcwMapFile(AcwMapFile);
            var resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap(ResourceNameCaseMap);

            if (CustomViewMapFile != null)
            {
                customViewMap = Xamarin.Android.Tasks.MonoAndroidHelper.LoadCustomViewMapFile(BuildEngine4, CustomViewMapFile);
            }

            var xmlFilesToUpdate = new Dictionary <string, string> ();

            for (int i = 0; i < SourceFiles.Length; i++)
            {
                var filename = SourceFiles [i].ItemSpec;

                if (File.Exists(filename))
                {
                    var ext             = Path.GetExtension(filename);
                    var destfilename    = DestinationFiles [i].ItemSpec;
                    var srcmodifiedDate = File.GetLastWriteTimeUtc(filename);
                    var dstmodifiedDate = File.Exists(destfilename) ? File.GetLastWriteTimeUtc(destfilename) : DateTime.MinValue;
                    var isXml           = ext == ".xml" || ext == ".axml";

                    Directory.CreateDirectory(Path.GetDirectoryName(destfilename));

                    if (isXml)
                    {
                        xmlFilesToUpdate.Add(filename, DestinationFiles [i].ItemSpec);
                        continue;
                    }
                    if (dstmodifiedDate < srcmodifiedDate && MonoAndroidHelper.CopyIfChanged(filename, destfilename))
                    {
                        // If the resource is not part of a raw-folder we strip away an eventual UTF-8 BOM
                        // This is a requirement for the Android designer because the desktop Java renderer
                        // doesn't support those type of BOM (it really wants the document to start
                        // with "<?"). Since there is no way to plug into the file saving mechanism in X.S
                        // we strip those here and point the designer to use resources from obj/
                        if (isXml && !MonoAndroidHelper.IsRawResourcePath(filename))
                        {
                            MonoAndroidHelper.CleanBOM(destfilename);
                        }

                        modifiedFiles.Add(new TaskItem(destfilename));
                    }
                }
                else
                {
                    Log.LogMessage("    Warning: input resource '{0}' was not found", filename);
                }
            }

            var merger = new ResourceMerger()
            {
                CacheFile = CacheFile,
                Log       = Log,
            };

            merger.Load();

            foreach (var p in xmlFilesToUpdate)
            {
                string filename        = p.Key;
                var    destfilename    = p.Value;
                var    srcmodifiedDate = File.GetLastWriteTimeUtc(filename);
                var    dstmodifiedDate = File.Exists(destfilename) ? File.GetLastWriteTimeUtc(destfilename) : DateTime.MinValue;

                var res = Path.Combine(Path.GetDirectoryName(filename), "..");
                MonoAndroidHelper.CopyIfChanged(filename, destfilename);
                MonoAndroidHelper.SetWriteable(destfilename);
                var updated = AndroidResource.UpdateXmlResource(res, destfilename, acw_map, logMessage: (level, message) => {
                    ITaskItem resdir = ResourceDirectories?.FirstOrDefault(x => filename.StartsWith(x.ItemSpec)) ?? null;
                    switch (level)
                    {
                    case TraceLevel.Error:
                        Log.FixupResourceFilenameAndLogCodedError("XA1002", message, filename, resdir.ItemSpec, resource_name_case_map);
                        break;

                    case TraceLevel.Warning:
                        Log.FixupResourceFilenameAndLogCodedWarning("XA1001", message, filename, resdir.ItemSpec, resource_name_case_map);
                        break;

                    default:
                        Log.LogDebugMessage(message);
                        break;
                    }
                }, registerCustomView: (e, file) => {
                    if (customViewMap == null)
                    {
                        return;
                    }
                    HashSet <string> set;
                    if (!customViewMap.TryGetValue(e, out set))
                    {
                        customViewMap.Add(e, set = new HashSet <string> ());
                    }
                    set.Add(file);
                });
                if (updated)
                {
                    if (!modifiedFiles.Any(i => i.ItemSpec == destfilename))
                    {
                        modifiedFiles.Add(new TaskItem(destfilename));
                    }
                }
            }
            merger.Save();
            ModifiedFiles = modifiedFiles.ToArray();

            Log.LogDebugTaskItems(" ModifiedFiles:", ModifiedFiles);

            if (customViewMap != null)
            {
                Xamarin.Android.Tasks.MonoAndroidHelper.SaveCustomViewMapFile(BuildEngine4, CustomViewMapFile, customViewMap);
            }

            return(true);
        }