/**
         * Locates the manifest file in the base directory, parses it and validates its contents. Any exceptions at this
         * stage will cause the promote to fail.
         * @return A ManifestParser which has been parsed and verified, and is ready to be used for a promotion.
         * @throws ExFatalError If the manifest cannot be loaded or verified.
         */
        private PromotionManifestParser loadManifest()
        {
            //Locate the manifest file in the base directory
            FileInfo lManifestFile;
            try
            {
                lManifestFile = resolveFile(MANIFEST_RELATIVE_FILE_PATH);
            }
            catch (FileNotFoundException e)
            {
                throw new ExFatalError("Cannot locate manifest file", e);
            }

            //Parse the manifest
            PromotionManifestParser lParser = new PromotionManifestParser(lManifestFile);
            try
            {
                lParser.parse();
            }
            catch (FileNotFoundException e)
            {
                throw new ExFatalError("Failed to parse manifest: file not found", e);
            }
            catch (IOException e)
            {
                throw new ExFatalError("Failed to parse manifest: IOException", e);
            }
            catch (ExParser e)
            {
                throw new ExFatalError("Failed to parse manifest: " + e.Message, e);
            }
            catch (ExManifest e)
            {
                throw new ExFatalError("Failed to load manifest: " + e.Message, e);
            }

            //Verify the manifest
            try
            {
                Logger.logInfo("Verifiying manifest...");
                lParser.verifyManifest(this);
            }
            catch (ExManifest e)
            {
                throw new ExFatalError("Manifest verification failed: " + e.Message, e);
            }

            Logger.logInfo("Manifest parsed and verified successfully");

            return lParser;
        }
        /**
         * Pre-parses all patch scripts in this promote, and returns a map of file paths to PatchScripts.
         * This should be performed in advance of the promote to catch any parsing issues before runtime. This method also
         * verifies that the scripts will be executed in the correct order.
         * @param pManifestParser ManifestParse containing all promotion files.
         * @return Map of file paths to PatchScripts.
         * @throws ExFatalError If there is an ordering problem or parsing problem.
         */
        private Dictionary<string, PatchScript> preParsePatchScripts(PromotionManifestParser pManifestParser)
        {

            Logger.logInfo("Validating patches...");

            Dictionary<string, PatchScript> lParsedScriptMap = new Dictionary<string, PatchScript>();
            //Map of patch labels to highest orders - used to verify script order is correct
            Dictionary<string, int?> lScriptNumberMap = new Dictionary<string, int?>();

            foreach (PromotionFile lPromotionFile in pManifestParser.getPromotionFileList())
            {
                if (BuiltInLoader.LOADER_NAME_PATCH == lPromotionFile.getLoaderName())
                {
                    try
                    {
                        PatchScript lScript = PatchScript.createFromPromotionFile(this, lPromotionFile);
                        int? lPreviousNumber = lScriptNumberMap[lScript.getPatchLabel()];

                        if (lPreviousNumber != null && lScript.getPatchNumber() < lPreviousNumber)
                        {
                            throw new ExFatalError("Patch order violation - " + lScript.getDisplayName() + " is implicated after patch with number " + lPreviousNumber);
                        }

                        if (lPromotionFile.isForcedDuplicate())
                        {
                            throw new ExFatalError("Patch " + lScript.getDisplayName() + " at position " + lPromotionFile.getSequencePosition() +
                                                   " cannot be a forced duplicate; patches may only be run once in a promote.");
                        };

                        lParsedScriptMap.Add(lPromotionFile.getFilePath(), lScript);
                        lScriptNumberMap.Add(lScript.getPatchLabel(), lScript.getPatchNumber());
                    }
                    catch (ExParser e)
                    {
                        throw new ExFatalError("Could not parse patch " + lPromotionFile.getFilePath() + ": " + e.Message, e);
                    }
                    catch (IOException e)
                    {
                        throw new ExFatalError("Could not parse patch " + lPromotionFile.getFilePath(), e);
                    }
                }
            }

            return lParsedScriptMap;

        }