/**
  * Creates the merging report file.
  *
  * @param mergingReport the merging activities report to serialize.
  */
 private void writeReport(MergingReport mergingReport) {
   FileWriter fileWriter = null;
   try {
     if (!mReportFile.get().getParentFile().exists()
         && !mReportFile.get().getParentFile().mkdirs()) {
       mLogger.warning(
           String.format(
               "Cannot create %1$s manifest merger report file,"
                   + "build will continue but merging activities "
                   + "will not be documented",
               mReportFile.get().getAbsolutePath()));
     } else {
       fileWriter = new FileWriter(mReportFile.get());
       mergingReport.getActions().log(fileWriter);
     }
   } catch (IOException e) {
     mLogger.warning(
         String.format(
             "Error '%1$s' while writing the merger report file, "
                 + "build can continue but merging activities "
                 + "will not be documented ",
             e.getMessage()));
   } finally {
     if (fileWriter != null) {
       try {
         fileWriter.close();
       } catch (IOException e) {
         mLogger.warning(
             String.format(
                 "Error '%1$s' while closing the merger report file, "
                     + "build can continue but merging activities "
                     + "will not be documented ",
                 e.getMessage()));
       }
     }
   }
 }
  /**
   * Perform high level ordering of files merging and delegates actual merging to {@link
   * XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)}
   *
   * @return the merging activity report.
   * @throws MergeFailureException if the merging cannot be completed (for instance, if xml files
   *     cannot be loaded).
   */
  private MergingReport merge() throws MergeFailureException {
    // initiate a new merging report
    MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger);

    SelectorResolver selectors = new SelectorResolver();
    // load all the libraries xml files up front to have a list of all possible node:selector
    // values.
    List<LoadedManifestInfo> loadedLibraryDocuments =
        loadLibraries(selectors, mergingReportBuilder);

    // load the main manifest file to do some checking along the way.
    LoadedManifestInfo loadedMainManifestInfo =
        load(
            new ManifestInfo(
                mManifestFile.getName(),
                mManifestFile,
                XmlDocument.Type.MAIN,
                Optional.<String>absent() /* mainManifestPackageName */),
            selectors,
            mergingReportBuilder);

    // first do we have a package declaration in the main manifest ?
    Optional<XmlAttribute> mainPackageAttribute =
        loadedMainManifestInfo.getXmlDocument().getPackage();
    if (!mainPackageAttribute.isPresent()) {
      mergingReportBuilder.addMessage(
          loadedMainManifestInfo.getXmlDocument().getSourceLocation(),
          0,
          0,
          MergingReport.Record.Severity.ERROR,
          String.format(
              "Main AndroidManifest.xml at %1$s manifest:package attribute " + "is not declared",
              loadedMainManifestInfo.getXmlDocument().getSourceLocation().print(true)));
      return mergingReportBuilder.build();
    }

    // check for placeholders presence.
    Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues;
    if (!mPlaceHolderValues.containsKey(APPLICATION_ID)) {
      finalPlaceHolderValues =
          ImmutableMap.<String, Object>builder()
              .putAll(mPlaceHolderValues)
              .put(PACKAGE_NAME, mainPackageAttribute.get().getValue())
              .put(APPLICATION_ID, mainPackageAttribute.get().getValue())
              .build();
    }

    // perform system property injection
    performSystemPropertiesInjection(mergingReportBuilder, loadedMainManifestInfo.getXmlDocument());

    // force the re-parsing of the xml as elements may have been added through system
    // property injection.
    loadedMainManifestInfo =
        new LoadedManifestInfo(
            loadedMainManifestInfo,
            loadedMainManifestInfo.getOriginalPackageName(),
            loadedMainManifestInfo.getXmlDocument().reparse());

    // invariant : xmlDocumentOptional holds the higher priority document and we try to
    // merge in lower priority documents.
    Optional<XmlDocument> xmlDocumentOptional = Optional.absent();
    for (File inputFile : mFlavorsAndBuildTypeFiles) {
      mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath());
      LoadedManifestInfo overlayDocument =
          load(
              new ManifestInfo(
                  null,
                  inputFile,
                  XmlDocument.Type.OVERLAY,
                  Optional.of(mainPackageAttribute.get().getValue())),
              selectors,
              mergingReportBuilder);

      // check package declaration.
      Optional<XmlAttribute> packageAttribute = overlayDocument.getXmlDocument().getPackage();
      // if both files declare a package name, it should be the same.
      if (loadedMainManifestInfo.getOriginalPackageName().isPresent()
          && packageAttribute.isPresent()
          && !loadedMainManifestInfo
              .getOriginalPackageName()
              .get()
              .equals(packageAttribute.get().getValue())) {
        // no suggestion for library since this is actually forbidden to change the
        // the package name per flavor.
        String message =
            mMergeType == MergeType.APPLICATION
                ? String.format(
                    "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
                        + "\thas a different value=(%3$s) "
                        + "declared in main manifest at %4$s\n"
                        + "\tSuggestion: remove the overlay declaration at %5$s "
                        + "\tand place it in the build.gradle:\n"
                        + "\t\tflavorName {\n"
                        + "\t\t\tapplicationId = \"%2$s\"\n"
                        + "\t\t}",
                    packageAttribute.get().printPosition(),
                    packageAttribute.get().getValue(),
                    mainPackageAttribute.get().getValue(),
                    mainPackageAttribute.get().printPosition(),
                    packageAttribute.get().getSourceLocation().print(true))
                : String.format(
                    "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
                        + "\thas a different value=(%3$s) "
                        + "declared in main manifest at %4$s",
                    packageAttribute.get().printPosition(),
                    packageAttribute.get().getValue(),
                    mainPackageAttribute.get().getValue(),
                    mainPackageAttribute.get().printPosition());
        mergingReportBuilder.addMessage(
            overlayDocument.getXmlDocument().getSourceLocation(),
            0,
            0,
            MergingReport.Record.Severity.ERROR,
            message);
        return mergingReportBuilder.build();
      }

      overlayDocument
          .getXmlDocument()
          .getRootNode()
          .getXml()
          .setAttribute("package", mainPackageAttribute.get().getValue());
      xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);

      if (!xmlDocumentOptional.isPresent()) {
        return mergingReportBuilder.build();
      }
    }

    mLogger.info("Merging main manifest %s\n", mManifestFile.getPath());
    xmlDocumentOptional = merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);

    if (!xmlDocumentOptional.isPresent()) {
      return mergingReportBuilder.build();
    }

    // force main manifest package into resulting merged file when creating a library manifest.
    if (mMergeType == MergeType.LIBRARY) {
      // extract the package name...
      String mainManifestPackageName =
          loadedMainManifestInfo.getXmlDocument().getRootNode().getXml().getAttribute("package");
      // save it in the selector instance.
      if (!Strings.isNullOrEmpty(mainManifestPackageName)) {
        xmlDocumentOptional
            .get()
            .getRootNode()
            .getXml()
            .setAttribute("package", mainManifestPackageName);
      }
    }
    for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
      mLogger.info("Merging library manifest " + libraryDocument.getLocation());
      xmlDocumentOptional = merge(xmlDocumentOptional, libraryDocument, mergingReportBuilder);
      if (!xmlDocumentOptional.isPresent()) {
        return mergingReportBuilder.build();
      }
    }

    // done with proper merging phase, now we need to trim unwanted elements, placeholder
    // substitution and system properties injection.
    ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder);
    if (mergingReportBuilder.hasErrors()) {
      return mergingReportBuilder.build();
    }

    // do one last placeholder substitution, this is useful as we don't stop the build
    // when a library failed a placeholder substitution, but the element might have
    // been overridden so the problem was transient. However, with the final document
    // ready, all placeholders values must have been provided.
    KeyBasedValueResolver<String> placeHolderValueResolver =
        new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues);
    PlaceholderHandler placeholderHandler = new PlaceholderHandler();
    placeholderHandler.visit(
        mMergeType, xmlDocumentOptional.get(), placeHolderValueResolver, mergingReportBuilder);
    if (mergingReportBuilder.hasErrors()) {
      return mergingReportBuilder.build();
    }

    // perform system property injection.
    performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());

    XmlDocument finalMergedDocument = xmlDocumentOptional.get();
    PostValidator.validate(finalMergedDocument, mergingReportBuilder);
    if (mergingReportBuilder.hasErrors()) {
      finalMergedDocument
          .getRootNode()
          .addMessage(
              mergingReportBuilder,
              MergingReport.Record.Severity.WARNING,
              "Post merge validation failed");
    }

    // only remove tools annotations if we are packaging an application.
    if (mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) {
      finalMergedDocument =
          ToolsInstructionsCleaner.cleanToolsReferences(finalMergedDocument, mLogger);
    }

    if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) {
      extractFcqns(finalMergedDocument);
    }

    if (finalMergedDocument != null) {
      mergingReportBuilder.setMergedDocument(finalMergedDocument);
    }

    MergingReport mergingReport = mergingReportBuilder.build();
    StdLogger stdLogger = new StdLogger(StdLogger.Level.INFO);
    mergingReport.log(stdLogger);
    stdLogger.verbose(mergingReport.getMergedDocument().get().prettyPrint());

    if (mReportFile.isPresent()) {
      writeReport(mergingReport);
    }

    return mergingReport;
  }