private void performPlaceHolderSubstitution(
      ManifestInfo manifestInfo,
      XmlDocument xmlDocument,
      MergingReport.Builder mergingReportBuilder) {

    // check for placeholders presence, switch first the packageName and application id if
    // it is not explicitly set.
    Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues;
    if (!mPlaceHolderValues.containsKey("applicationId")) {
      String packageName =
          manifestInfo.getMainManifestPackageName().isPresent()
              ? manifestInfo.getMainManifestPackageName().get()
              : xmlDocument.getPackageName();
      // add all existing placeholders except package name that will be swapped.
      ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder();
      for (Map.Entry<String, Object> entry : mPlaceHolderValues.entrySet()) {
        if (!entry.getKey().equals(PlaceholderHandler.PACKAGE_NAME)) {
          builder.put(entry);
        }
      }
      finalPlaceHolderValues =
          builder
              .put(PlaceholderHandler.PACKAGE_NAME, packageName)
              .put(PlaceholderHandler.APPLICATION_ID, packageName)
              .build();
    }

    KeyBasedValueResolver<String> placeHolderValueResolver =
        new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues);
    PlaceholderHandler placeholderHandler = new PlaceholderHandler();
    placeholderHandler.visit(
        mMergeType, xmlDocument, placeHolderValueResolver, mergingReportBuilder);
  }
  public void testPlaceholders() throws ParserConfigurationException, SAXException, IOException {

    String xml =
        ""
            + "<manifest\n"
            + "    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
            + "    <activity android:name=\"activityOne\"\n"
            + "         android:attr1=\"${landscapePH}\"\n"
            + "         android:attr2=\"prefix.${landscapePH}\"\n"
            + "         android:attr3=\"${landscapePH}.suffix\"\n"
            + "         android:attr4=\"prefix${landscapePH}suffix\">\n"
            + "    </activity>\n"
            + "</manifest>";

    XmlDocument refDocument =
        TestUtils.xmlDocumentFromString(
            TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);

    PlaceholderHandler handler = new PlaceholderHandler();
    handler.visit(
        ManifestMerger2.MergeType.APPLICATION,
        refDocument,
        new KeyBasedValueResolver<String>() {
          @Override
          public String getValue(@NonNull String key) {
            return "newValue";
          }
        },
        mBuilder);

    Optional<XmlElement> activityOne =
        refDocument
            .getRootNode()
            .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, ".activityOne");
    assertTrue(activityOne.isPresent());
    assertEquals(5, activityOne.get().getAttributes().size());
    // check substitution.
    assertEquals(
        "newValue",
        activityOne.get().getAttribute(XmlNode.fromXmlName("android:attr1")).get().getValue());
    assertEquals(
        "prefix.newValue",
        activityOne.get().getAttribute(XmlNode.fromXmlName("android:attr2")).get().getValue());
    assertEquals(
        "newValue.suffix",
        activityOne.get().getAttribute(XmlNode.fromXmlName("android:attr3")).get().getValue());
    assertEquals(
        "prefixnewValuesuffix",
        activityOne.get().getAttribute(XmlNode.fromXmlName("android:attr4")).get().getValue());

    for (XmlAttribute xmlAttribute : activityOne.get().getAttributes()) {
      // any attribute other than android:name should have been injected.
      if (!xmlAttribute.getName().toString().contains("name")) {
        verify(mActionRecorder)
            .recordAttributeAction(
                xmlAttribute, SourcePosition.UNKNOWN, Actions.ActionType.INJECTED, null);
      }
    }
  }
  public void testPlaceHolder_notProvided_inLibrary()
      throws ParserConfigurationException, SAXException, IOException {
    String xml =
        ""
            + "<manifest\n"
            + "    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
            + "    <activity android:name=\"activityOne\"\n"
            + "         android:attr1=\"${landscapePH}\"/>\n"
            + "</manifest>";

    XmlDocument refDocument =
        TestUtils.xmlDocumentFromString(
            TestUtils.sourceFile(getClass(), "testPlaceholders#xml"), xml);

    PlaceholderHandler handler = new PlaceholderHandler();
    handler.visit(ManifestMerger2.MergeType.LIBRARY, refDocument, nullResolver, mBuilder);
    // verify the error was recorded.
    verify(mBuilder)
        .addMessage(
            any(SourceFilePosition.class), eq(MergingReport.Record.Severity.INFO), anyString());
  }
  /**
   * 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;
  }