content: /*
   *
   *
   *
   *                                   Apache License
   *                             Version 2.0, January 2004
   *                          http://www.apache.org/licenses/
   *
   *     TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
   *
   *     1. Definitions.
   *
   *        "License" shall mean the terms and conditions for use, reproduction,
   *        and distribution as defined by Sections 1 through 9 of this document.
   *
   *        "Licensor" shall mean the copyright owner or entity authorized by
   *        the copyright owner that is granting the License.
   *
   *        "Legal Entity" shall mean the union of the acting entity and all
   *        other entities that control, are controlled by, or are under common
   *        control with that entity. For the purposes of this definition,
   *        "control" means (i) the power, direct or indirect, to cause the
   *        direction or management of such entity, whether by contract or
   *        otherwise, or (ii) ownership of fifty percent (50%) or more of the
   *        outstanding shares, or (iii) beneficial ownership of such entity.
   *
   *        "You" (or "Your") shall mean an individual or Legal Entity
   *        exercising permissions granted by this License.
   *
   *        "Source" form shall mean the preferred form for making modifications,
   *        including but not limited to software source code, documentation
   *        source, and configuration files.
   *
   *        "Object" form shall mean any form resulting from mechanical
   *        transformation or translation of a Source form, including but
   *        not limited to compiled object code, generated documentation,
   *        and conversions to other media types.
   *
   *        "Work" shall mean the work of authorship, whether in Source or
   *        Object form, made available under the License, as indicated by a
   *        copyright notice that is included in or attached to the work
   *        (an example is provided in the Appendix below).
   *
   *        "Derivative Works" shall mean any work, whether in Source or Object
   *        form, that is based on (or derived from) the Work and for which the
   *        editorial revisions, annotations, elaborations, or other modifications
   *        represent, as a whole, an original work of authorship. For the purposes
   *        of this License, Derivative Works shall not include works that remain
   *        separable from, or merely link (or bind by name) to the interfaces of,
   *        the Work and Derivative Works thereof.
   *
   *        "Contribution" shall mean any work of authorship, including
   *        the original version of the Work and any modifications or additions
   *        to that Work or Derivative Works thereof, that is intentionally
   *        submitted to Licensor for inclusion in the Work by the copyright owner
   *        or by an individual or Legal Entity authorized to submit on behalf of
   *        the copyright owner. For the purposes of this definition, "submitted"
   *        means any form of electronic, verbal, or written communication sent
   *        to the Licensor or its representatives, including but not limited to
   *        communication on electronic mailing lists, source code control systems,
   *        and issue tracking systems that are managed by, or on behalf of, the
   *        Licensor for the purpose of discussing and improving the Work, but
   *        excluding communication that is conspicuously marked or otherwise
   *        designated in writing by the copyright owner as "Not a Contribution."
   *
   *        "Contributor" shall mean Licensor and any individual or Legal Entity
   *        on behalf of whom a Contribution has been received by Licensor and
   *        subsequently incorporated within the Work.
   *
   *     2. Grant of Copyright License. Subject to the terms and conditions of
   *        this License, each Contributor hereby grants to You a perpetual,
   *        worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   *        copyright license to reproduce, prepare Derivative Works of,
   *        publicly display, publicly perform, sublicense, and distribute the
   *        Work and such Derivative Works in Source or Object form.
   *
   *     3. Grant of Patent License. Subject to the terms and conditions of
   *        this License, each Contributor hereby grants to You a perpetual,
   *        worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   *        (except as stated in this section) patent license to make, have made,
   *        use, offer to sell, sell, import, and otherwise transfer the Work,
   *        where such license applies only to those patent claims licensable
   *        by such Contributor that are necessarily infringed by their
   *        Contribution(s) alone or by combination of their Contribution(s)
   *        with the Work to which such Contribution(s) was submitted. If You
   *        institute patent litigation against any entity (including a
   *        cross-claim or counterclaim in a lawsuit) alleging that the Work
   *        or a Contribution incorporated within the Work constitutes direct
   *        or contributory patent infringement, then any patent licenses
   *        granted to You under this License for that Work shall terminate
   *        as of the date such litigation is filed.
   *
   *     4. Redistribution. You may reproduce and distribute copies of the
   *        Work or Derivative Works thereof in any medium, with or without
   *        modifications, and in Source or Object form, provided that You
   *        meet the following conditions:
   *
   *        (a) You must give any other recipients of the Work or
   *            Derivative Works a copy of this License; and
   *
   *        (b) You must cause any modified files to carry prominent notices
   *            stating that You changed the files; and
   *
   *        (c) You must retain, in the Source form of any Derivative Works
   *            that You distribute, all copyright, patent, trademark, and
   *            attribution notices from the Source form of the Work,
   *            excluding those notices that do not pertain to any part of
   *            the Derivative Works; and
   *
   *        (d) If the Work includes a "NOTICE" text file as part of its
   *            distribution, then any Derivative Works that You distribute must
   *            include a readable copy of the attribution notices contained
   *            within such NOTICE file, excluding those notices that do not
   *            pertain to any part of the Derivative Works, in at least one
   *            of the following places: within a NOTICE text file distributed
   *            as part of the Derivative Works; within the Source form or
   *            documentation, if provided along with the Derivative Works; or,
   *            within a display generated by the Derivative Works, if and
   *            wherever such third-party notices normally appear. The contents
   *            of the NOTICE file are for informational purposes only and
   *            do not modify the License. You may add Your own attribution
   *            notices within Derivative Works that You distribute, alongside
   *            or as an addendum to the NOTICE text from the Work, provided
   *            that such additional attribution notices cannot be construed
   *            as modifying the License.
   *
   *        You may add Your own copyright statement to Your modifications and
   *        may provide additional or different license terms and conditions
   *        for use, reproduction, or distribution of Your modifications, or
   *        for any such Derivative Works as a whole, provided Your use,
   *        reproduction, and distribution of the Work otherwise complies with
   *        the conditions stated in this License.
   *
   *     5. Submission of Contributions. Unless You explicitly state otherwise,
   *        any Contribution intentionally submitted for inclusion in the Work
   *        by You to the Licensor shall be under the terms and conditions of
   *        this License, without any additional terms or conditions.
   *        Notwithstanding the above, nothing herein shall supersede or modify
   *        the terms of any separate license agreement you may have executed
   *        with Licensor regarding such Contributions.
   *
   *     6. Trademarks. This License does not grant permission to use the trade
   *        names, trademarks, service marks, or product names of the Licensor,
   *        except as required for reasonable and customary use in describing the
   *        origin of the Work and reproducing the content of the NOTICE file.
   *
   *     7. Disclaimer of Warranty. Unless required by applicable law or
   *        agreed to in writing, Licensor provides the Work (and each
   *        Contributor provides its Contributions) on an "AS IS" BASIS,
   *        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   *        implied, including, without limitation, any warranties or conditions
   *        of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
   *        PARTICULAR PURPOSE. You are solely responsible for determining the
   *        appropriateness of using or redistributing the Work and assume any
   *        risks associated with Your exercise of permissions under this License.
   *
   *     8. Limitation of Liability. In no event and under no legal theory,
   *        whether in tort (including negligence), contract, or otherwise,
   *        unless required by applicable law (such as deliberate and grossly
   *        negligent acts) or agreed to in writing, shall any Contributor be
   *        liable to You for damages, including any direct, indirect, special,
   *        incidental, or consequential damages of any character arising as a
   *        result of this License or out of the use or inability to use the
   *        Work (including but not limited to damages for loss of goodwill,
   *        work stoppage, computer failure or malfunction, or any and all
   *        other commercial damages or losses), even if such Contributor
   *        has been advised of the possibility of such damages.
   *
   *     9. Accepting Warranty or Additional Liability. While redistributing
   *        the Work or Derivative Works thereof, You may choose to offer,
   *        and charge a fee for, acceptance of support, warranty, indemnity,
   *        or other liability obligations and/or rights consistent with this
   *        License. However, in accepting such obligations, You may act only
   *        on Your own behalf and on Your sole responsibility, not on behalf
   *        of any other Contributor, and only if You agree to indemnify,
   *        defend, and hold each Contributor harmless for any liability
   *        incurred by, or claims asserted against, such Contributor by reason
   *        of your accepting any such warranty or additional liability.
   *
   *     END OF TERMS AND CONDITIONS
   *
   *     APPENDIX: How to apply the Apache License to your work.
   *
   *        To apply the Apache License to your work, attach the following
   *        boilerplate notice, with the fields enclosed by brackets "[]"
   *        replaced with your own identifying information. (Don't include
   *        the brackets!)  The text should be enclosed in the appropriate
   *        comment syntax for the file format. We also recommend that a
   *        file or class name and description of purpose be included on the
   *        same "printed page" as the copyright notice for easier
   *        identification within third-party archives.
   *
   *     Copyright 2016 Alibaba Group
   *
   *     Licensed under the Apache License, Version 2.0 (the "License");
   *     you may not use this file except in compliance with the License.
   *     You may obtain a copy of the License at
   *
   *         http://www.apache.org/licenses/LICENSE-2.0
   *
   *     Unless required by applicable law or agreed to in writing, software
   *     distributed under the License is distributed on an "AS IS" BASIS,
   *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *     See the License for the specific language governing permissions and
   *     limitations under the License.
   *
   *
   */
  
  package com.taobao.android.builder.tools.manifest;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import javax.xml.xpath.XPath;
  import javax.xml.xpath.XPathExpressionException;
  
  import com.alibaba.fastjson.JSON;
  import com.alibaba.fastjson.TypeReference;
  
  import com.android.xml.AndroidXPathFactory;
  import com.google.common.collect.HashBasedTable;
  import com.google.common.collect.Maps;
  import com.google.common.collect.Multimap;
  import com.google.common.collect.Table;
  import com.taobao.android.builder.extension.ManifestOptions;
  import com.taobao.android.builder.tools.bundleinfo.model.BundleInfo;
  import com.taobao.android.builder.tools.manifest.Permission.Item;
  import com.taobao.android.builder.tools.xml.XmlHelper;
  import org.apache.commons.io.FileUtils;
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.lang.StringUtils;
  import org.dom4j.Attribute;
  import org.dom4j.Comment;
  import org.dom4j.Document;
  import org.dom4j.DocumentException;
  import org.dom4j.Element;
  import org.dom4j.Node;
  import org.dom4j.Visitor;
  import org.dom4j.VisitorSupport;
  import org.dom4j.io.OutputFormat;
  import org.dom4j.io.SAXReader;
  import org.dom4j.io.XMLWriter;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.xml.sax.InputSource;
  
  /**
   * @author shenghua.nish
   * @date 2015-04-22 At 10:58
   */
  public class ManifestFileUtils {
  
      private static final Logger logger = LoggerFactory.getLogger(ManifestFileUtils.class);
  
      public static String[] SYSTEM_PERMISSION = new String[] {"android.permission", "com.android"};
  
      private static final String INSTANT_RUN_CONTENTPROVIDER = "com.android.tools.ir.server.InstantRunContentProvider";
  
      private static final String ALI_INSTANT_RUN_CONTENTPROVIDER = "com.android.alibaba.ip.server.InstantRunContentProvider";
  
  
      /**
       * Follow up the manifest
       *  @param mainManifest
       * @param libManifestMap
       * @param baseBunfleInfoFile
       * @param manifestOptions
       * @param debuggable
       */
      public static Result postProcessManifests(File mainManifest, Map<String, File> libManifestMap,
                                                Multimap<String, File> libDependenciesMaps, File baseBunfleInfoFile,
                                                ManifestOptions manifestOptions, boolean addMultiDex,
                                                boolean isInstantRun, boolean debuggable, Set<String> remoteBundles, Set<String> insideBundles, boolean pushInstall)
          throws IOException, DocumentException {
  
          Result result = new Result();
  
          File inputFile = new File(mainManifest.getParentFile(), "AndroidManifest-backup.xml");
          FileUtils.deleteQuietly(inputFile);
          FileUtils.moveFile(mainManifest, inputFile);
  
          Document document = XmlHelper.readXml(inputFile);
  
          if (null != baseBunfleInfoFile && baseBunfleInfoFile.exists()) {
              addApplicationMetaData(document, libManifestMap, baseBunfleInfoFile, manifestOptions, remoteBundles,insideBundles);
          }
  
          if (null != manifestOptions && manifestOptions.isAddAtlasProxyComponents()) {
              AtlasProxy.addAtlasProxyClazz(document, manifestOptions.getAtlasProxySkipChannels(), result);
          }
  
              addAndroidLabel(document,pushInstall);
  
          if (null != manifestOptions && manifestOptions.isAddBundleLocation()) {
              addBundleLocationToDestManifest(document, libManifestMap, libDependenciesMaps, manifestOptions);
          }
          if (null != manifestOptions && manifestOptions.isReplaceApplication()) {
              replaceManifestApplicationName(document);
          }
  
          if ((null != manifestOptions && manifestOptions.isAddMultiDexMetaData()) || addMultiDex) {
              addMultiDexMetaData(document);
          }
          if (null != manifestOptions && manifestOptions.isRemoveProvider()) {
              removeProvider(document);
          }
          if (isInstantRun) {
              singleProcess(document,ManifestFileUtils.getApplicationId(inputFile));
          }
  
          if (isInstantRun && !debuggable){
              disableDebuggable(document);
  
          }
          removeCustomLaunches(document, manifestOptions);
          updatePermission(document, manifestOptions);
          removeComments(document);
  
          XmlHelper.saveDocument(document, mainManifest);
  
          printlnPermissions(document);
  
          return result;
      }
  
      private static void singleProcess(Document document, String applicationId) {
              List<Node> nodes = document.getRootElement().selectNodes("//provider");
          for (Node node : nodes) {
              Element element = (Element)node;
              String value = element.attributeValue("name");
              if (value.equals(INSTANT_RUN_CONTENTPROVIDER)){
                  element.addAttribute("name",ALI_INSTANT_RUN_CONTENTPROVIDER);
                  element.addAttribute("authorities",applicationId+"."+ALI_INSTANT_RUN_CONTENTPROVIDER);
                  Attribute attribute = element.attribute("multiprocess");
                  if (attribute!= null) {
                      attribute.setValue("false");
                      logger.warn("singleProcess  com.android.tools.ir.server.InstantRunContentProvider.......");
                  }
  
              }
  
          }
  
      }
  
      private static void disableDebuggable(Document document) {
          Element root = document.getRootElement();// Get the root node
          Element app = root.element("application");
          Attribute attribute = app.attribute("debuggable");
          if (attribute!= null){
              attribute.setValue("false");
              logger.warn("disable android:debuggable .......");
  //            app.remove(attribute);
          }
      }
  
      private static void addAndroidLabel(Document document,boolean pushInstall) {
          Element root = document.getRootElement();// Get the root node
          Element applicationElement = root.element("application");
          Attribute attribute = applicationElement.attribute("android:extractNativeLibs");
          if (attribute == null && pushInstall){
              applicationElement.addAttribute("android:extractNativeLibs","false");
          }else if (pushInstall){
              attribute.setValue("false");
          }else if (!pushInstall && attribute!= null){
              applicationElement.remove(attribute);
          }
      }
  
      /**
       * Replace the original application in the manifest nameFor AtlasBridgeApplication
       * The original name has been written in meta-data
       */
      private static void replaceManifestApplicationName(Document document) {
          // Write meta-data information
          Element root = document.getRootElement();// Get the root node
          Element applicationElement = root.element("application");
          String realApplicationClassName = applicationElement.attributeValue("name");
  
          if (null == realApplicationClassName) {
              realApplicationClassName = "";
          }
  
          applicationElement.addAttribute(StringUtils.isEmpty(realApplicationClassName) ? "android:name" : "name",
                                          "android.taobao.atlas.startup.AtlasBridgeApplication");
  
          Element metaData = applicationElement.addElement("meta-data");
          metaData.addAttribute("android:name", "REAL_APPLICATION");
          metaData.addAttribute("android:value", realApplicationClassName);
      }
  
      private static void addMultiDexMetaData(Document document) {
          // Write meta-data information
          Element root = document.getRootElement();// Get the root node
          Element applicationElement = root.element("application");
  
          Element metaData = applicationElement.addElement("meta-data");
          metaData.addAttribute("android:name", "multidex_enable");
          metaData.addAttribute("android:value", "true");
      }
  
      /**
       * Add the metadata information to the manifest of the Lord as a solution for atals
       *
       * @param document
       * @param libManifestMap
       * @param baseBunfleInfoFile
       * @param manifestOptions
       */
      private static void addApplicationMetaData(Document document, Map<String, File> libManifestMap,
                                                 File baseBunfleInfoFile, ManifestOptions manifestOptions,
                                                 Set<String> remoteBundles,Set<String>insideBundles) throws IOException, DocumentException {
          Map<String, BundleInfo> bundleFileMap = Maps.newHashMap();
          // Parsing basic information
          if (null != baseBunfleInfoFile && baseBunfleInfoFile.exists() && baseBunfleInfoFile.canRead()) {
              String bundleBaseInfo = FileUtils.readFileToString(baseBunfleInfoFile, "utf-8");
              bundleFileMap = JSON.parseObject(bundleBaseInfo, new TypeReference<Map<String, BundleInfo>>() {});
          }
          Map<String, LibBundleInfo> awbManifestMap = Maps.newHashMap();
          for (Map.Entry<String, File> entry : libManifestMap.entrySet()) {
              String artifactId = entry.getKey();
  
              String libName = artifactId.substring(artifactId.indexOf("-") + 1);
  
              File libManifest = entry.getValue();
              if (libManifest.exists()) {
                  SAXReader reader = new SAXReader();
                  Document libDocument = reader.read(libManifest);// Read the XML file
                  Element libRoot = libDocument.getRootElement();// Get the root node
                  String packageName = libRoot.attributeValue("package");
                  Element applicationElement = libRoot.element("application");
                  String applicationName = null;
                  if (null != applicationElement) {
                      applicationName = applicationElement.attributeValue("name");
                  }
                  LibBundleInfo libBundleInfo = new LibBundleInfo(artifactId, packageName, applicationName,
                                                                  bundleFileMap.get(libName), libName);
                  awbManifestMap.put(artifactId, libBundleInfo);
              }
          }
  
          // Write meta-data information
          Element root = document.getRootElement();// Get the root node
          List<? extends Node> nodes = root.selectNodes("//application");
          for (Node node : nodes) {
              Element element = (Element)node;
              for (String artifactId : libManifestMap.keySet()) {
                  LibBundleInfo libBundleInfo = awbManifestMap.get(artifactId);
                  if (StringUtils.isNotBlank(libBundleInfo.applicationName)) {
                      String bundlePackageName = libBundleInfo.packageName;
                      BundleInfo bundleInfo = libBundleInfo.bundleInfo;
                      Element metaData = element.addElement("meta-data");
  
                      String bundleDepValue = "";
                      if (null != bundleInfo && bundleInfo.getDependency() != null) {
                          bundleDepValue = StringUtils.join(bundleInfo.getDependency(), "|");
                      }
                      String value = libBundleInfo.applicationName + "," + !(remoteBundles.contains(libBundleInfo.libName)||insideBundles.contains(libBundleInfo.libName))
                          + "," + bundleDepValue;
                      logger.info("[bundleInfo] add bundle value : " + value + " to manifest");
                      metaData.addAttribute("android:name", "bundle_" + bundlePackageName);
                      metaData.addAttribute("android:value", value);
                  }
              }
          }
      }
  
      static class LibBundleInfo {
  
          String name;
  
          String packageName;
  
          String applicationName;
  
          BundleInfo bundleInfo;
  
          String libName;
  
          public LibBundleInfo(String name, String packageName, String applicationName, BundleInfo bundleInfo,
                               String libName) {
              this.name = name;
              this.packageName = packageName;
              this.applicationName = applicationName;
              this.bundleInfo = bundleInfo;
              this.libName = libName;
          }
      }
  
      /**
       * Add bundleLocation to the manifest file
       *
       * @param document
       * @param libManifestMap
       * @param manifestOptions
       * @throws DocumentException
       */
      private static void addBundleLocationToDestManifest(Document document, Map<String, File> libManifestMap,
                                                          Multimap<String, File> libDependenciesMaps,
                                                          ManifestOptions manifestOptions) throws DocumentException {
          Table<String, String, String> bundleInfoTable = HashBasedTable.create();
          Map<String, String> packageNameMap = new HashMap<String, String>();
          for (Map.Entry<String, File> entry : libManifestMap.entrySet()) {
              File libManifest = entry.getValue();
              if (libManifest.exists()) {
                  SAXReader reader = new SAXReader();
                  Document libDocument = reader.read(libManifest);// Read the XML file
                  Element libRoot = libDocument.getRootElement();// Get the root node
                  String packageName = libRoot.attributeValue("package");
                  packageNameMap.put(entry.getKey(), packageName);
                  List<? extends Node> nodes = libRoot.selectNodes("//activity|//service|//receiver");
                  for (Node node : nodes) {
                      Element e = (Element)node;
                      String name = e.attributeValue("name");
                      String type = e.getName();
                      bundleInfoTable.put(type, name, packageName);
                  }
              }
          }
  
          // Gets all dependent manifest information
          for (String key : libDependenciesMaps.keySet()) {
              Collection<File> libManifests = libDependenciesMaps.get(key);
              for (File libManifest : libManifests) {
                  if (libManifest.exists()) {
                      SAXReader reader = new SAXReader();
                      Document libDocument = reader.read(libManifest);// Read the XML file
                      Element libRoot = libDocument.getRootElement();// Get the root node
                      String packageName = packageNameMap.get(key);
                      List<? extends Node> nodes = libRoot.selectNodes("//activity|//service|//receiver");
                      for (Node node : nodes) {
                          Element e = (Element)node;
                          String name = e.attributeValue("name");
                          String type = e.getName();
                          bundleInfoTable.put(type, name, packageName);
                      }
                  }
              }
          }
  
          Element root = document.getRootElement();// Get the root node
          List<? extends Node> nodes = root.selectNodes("//activity|//service|//receiver");
          for (Node node : nodes) {
              Element e = (Element)node;
              String name = e.attributeValue("name");
              String type = e.getName();
              String packageName = bundleInfoTable.get(type, name);
              if (null != packageName) {
                  Element metaData = e.addElement("meta-data");
                  metaData.addAttribute("android:name", "bundleLocation");
                  metaData.addAttribute("android:value", packageName);
              }
          }
      }
  
      /**
       * Delete the custom header
       *
       * @param document
       * @param manifestOptions
       */
      private static void removeCustomLaunches(Document document, ManifestOptions manifestOptions) {
          if (null == manifestOptions) {
              return;
          }
          Element root = document.getRootElement();// Get the root node
          // Update launch information
          if (manifestOptions.getRetainLaunches() != null && manifestOptions.getRetainLaunches().size() > 0) {
              List<? extends Node> nodes = root.selectNodes(
                  "//activity/intent-filter/category|//activity-alias/intent-filter/category");
              for (Node node : nodes) {
                  Element e = (Element)node;
                  if ("android.intent.category.LAUNCHER".equalsIgnoreCase(e.attributeValue("name"))) {
                      Element activityElement = e.getParent().getParent();
                      String activiyName = activityElement.attributeValue("name");
                      if (!manifestOptions.getRetainLaunches().contains(activiyName)) {
                          if (activityElement.getName().equalsIgnoreCase("activity-alias")) {
                              activityElement.getParent().remove(activityElement);
                          } else {
                              e.getParent().remove(e);
                          }
                      }
                  }
              }
          }
      }
  
      /**
       * Set the permissions for the manifest
       *
       * @param document
       * @param manifestOptions
       */
      private static void updatePermission(Document document, ManifestOptions manifestOptions) throws IOException {
          if (null == manifestOptions) {
              return;
          }
  
          Element root = document.getRootElement();// Get the root node
  
          if (null != manifestOptions.getPermissionJsonFile() && manifestOptions.getPermissionJsonFile().exists()) {
              String json = FileUtils.readFileToString(manifestOptions.getPermissionJsonFile());
              Permission permission = JSON.parseObject(json, Permission.class);
  
              List<Node> nodes = root.selectNodes("//permission");
              List<Node> nodes1 = root.selectNodes("//uses-permission");
              List<Node> nodes2 = root.selectNodes("//uses-feature");
  
              for (Node node : nodes) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  Item item = Permission.query(permission.getPermissions(), name);
                  if (null == item) {
                      logger.warn("[permission] remove " + name);
                      element.getParent().remove(element);
                  }
              }
  
              for (Node node : nodes1) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  Item item = Permission.query(permission.getUses_permissions(), name);
                  if (null == item) {
                      logger.warn("[uses-permission] remove " + name);
                      element.getParent().remove(element);
                  }
              }
  
              for (Node node : nodes2) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  if (StringUtils.isEmpty(name)){
                      continue;
                  }
                  Item item = Permission.query(permission.getUses_features(), name);
                  if (null == item) {
                      logger.warn("[uses-feature] remove " + name);
                      element.getParent().remove(element);
                  } else {
                      //Modify the value
                      if (StringUtils.isNotEmpty(item.getValue())) {
                          Attribute required = element.attribute("required");
                          if (null != required){
                              required.setValue(item.getValue());
                          }
                          //element.addAttribute(new QName("required", new Namespace("android", null)), item.getValue());
                      }
                  }
              }
  
              return;
          }
  
          if (null != manifestOptions.getPermissionListFile() && manifestOptions.getPermissionListFile().exists()) {
              List<String> whiteList = FileUtils.readLines(manifestOptions.getPermissionListFile());
              List<Node> nodes = new ArrayList<>();
              nodes.addAll(root.selectNodes("//permission"));
              nodes.addAll(root.selectNodes("//uses-permission"));
  
              for (Node node : nodes) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  if (!whiteList.contains(name)) {
                      logger.warn("[permission] remove " + name);
                      element.getParent().remove(element);
                  }
              }
  
              return;
          }
  
          // Update custom permissions
          if (manifestOptions.isRemoveCustomPermission()) {
              List<? extends Node> nodes = root.selectNodes("//permission");
              for (Node node : nodes) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  boolean retain = false;
                  for (String systemPermission : SYSTEM_PERMISSION) {
                      if (name.startsWith(systemPermission)) {
                          retain = true;
                          break;
                      }
                  }
                  // If there is a custom permission
                  if (null != manifestOptions.getRetainPermissions()
                      && manifestOptions.getRetainPermissions().size() > 0) {
                      for (String retainPermission : manifestOptions.getRetainPermissions()) {
                          if (name.startsWith(retainPermission)) {
                              retain = true;
                              break;
                          }
                      }
                  }
                  if (!retain) {
                      element.getParent().remove(element);
                  }
              }
          }
  
          // Update system permissions
          if (null != manifestOptions.getRemoveSystemPermissions()
              && manifestOptions.getRemoveSystemPermissions().size() > 0) {
              List<? extends Node> nodes = root.selectNodes("//uses-permission");
              for (Node node : nodes) {
                  Element element = (Element)node;
                  String name = element.attributeValue("name");
                  Set<String> removedPermissions = manifestOptions.getRemoveSystemPermissions();
                  if (removedPermissions.contains(name)) {
                      element.getParent().remove(element);
                  }
              }
          }
      }
  
      public static void printlnPermissions(Document document) throws IOException {
          Element root = document.getRootElement();// Get the root node
          List<Node> nodes = new ArrayList<>();
          nodes.addAll(root.selectNodes("//permission"));
          nodes.addAll(root.selectNodes("//uses-permission"));
  
          Set<String> permissons = new HashSet<>();
          for (Node node : nodes) {
              Element element = (Element)node;
              String name = element.attributeValue("name");
              permissons.add(name);
          }
  
          logger.warn("[permission] start >>>>>>>> ");
          List<String> lines = new ArrayList<>(permissons);
          Collections.sort(lines);
          for (String name : lines) {
              logger.warn(name);
          }
          logger.warn("[permission] end <<<<<<<<< ");
      }
  
      /**
       * Remove comments from XML
       *
       * @param document
       * @throws IOException
       * @throws DocumentException
       */
      private static void removeComments(Document document) throws IOException, DocumentException {
          Visitor visitor = new VisitorSupport() {
  
              @Override
              public void visit(Comment comment) {
                  comment.setText(" ");
              }
          };
          document.accept(visitor);
      }
  
      /**
       * Get the packageId for the manifest file
       *
       * @param manifestFile
       * @return
       */
      public static String getApplicationId(File manifestFile) {
          SAXReader reader = new SAXReader();
          if (manifestFile.exists()) {
              Document document = null;// Read the XML file
              try {
                  document = reader.read(manifestFile);
                  Element root = document.getRootElement();// Get the root node
                  String packageName = root.attributeValue("package");
                  return packageName;
              } catch (DocumentException e) {
                  e.printStackTrace();
              }
          }
          return null;
      }
      //
      ///**
      // * Preprocess the specified manifest file
      // *
      // * @param mainManifestFile
      // * @param libManifestFiles
      // * @param updateSdkVersion
      // */
      //@Deprecated
      //public static void preProcessManifests(File mainManifestFile,
      //                                       List<File> libManifestFiles,
      //                                       boolean updateSdkVersion) throws DocumentException, IOException {
      //    ManifestFileObject mainManifestFileObject = getManifestFileObject(mainManifestFile);
      //    for (File libManifestFile : libManifestFiles) {
      //        // Write files
      //        updatePreProcessManifestFile(libManifestFile, mainManifestFileObject, updateSdkVersion);
      //    }
      //
      //    //Get the bundleInfo information
      //
      //}
  
      public static void updatePreProcessBaseManifestFile(File modifyManifest, File orgManifestFile)
          throws IOException, DocumentException {
  
          Document document = XmlHelper.readXml(orgManifestFile);// Read the XML file
  
          Element root = document.getRootElement();// Get the root node
          root.addNamespace("tools", "http://schemas.android.com/tools");
          Element applicationElement = root.element("application");
  
          //Determines whether there is application and needs to be deleted
          if (null != applicationElement) {
              applicationElement.addAttribute("tools:replace",
                                              "android:name,android:icon,android:allowBackup,android:label,"
                                                  + "android:supportsRtl");
          }
  
          XmlHelper.saveDocument(document, modifyManifest);
      }
  
      /**
       * Update the libManifest file
       *
       * @param modifyManifest
       * @param mainManifestFileObject param updateSdkVersion
       * @param incremental
       */
  
      public static void updatePreProcessManifestFile(File modifyManifest, File orgManifestFile,
                                                      ManifestInfo mainManifestFileObject, boolean updateSdkVersion,
                                                      boolean incremental) throws IOException, DocumentException {
  
          Document document = XmlHelper.readXml(orgManifestFile);// Read the XML file
  
          Element root = document.getRootElement();// Get the root node
          if (updateSdkVersion) {
              Element useSdkElement = root.element("uses-sdk");
              if (null != useSdkElement) {
                  useSdkElement.getParent().remove(useSdkElement);
              }
          }
  
          // First, we will manually process the tools:remove and tools:replace rules, and find that some of them are not necessarily possible through the ManifestMerge
          Element applicationElement = root.element("application");
  
          List<Node> supportVersion = root.selectNodes("//meta-data");
  
          //Determines whether there is application and needs to be deleted
          if (null != applicationElement) {
              Attribute attribute = applicationElement.attribute("name");
              if (null != attribute) {
                  applicationElement.remove(attribute);
              }
          }
  
          for (Node node : supportVersion) {
              Element element = (Element)node;
              Attribute attribute = element.attribute("name");
              if (attribute != null) {
                  if (attribute.getValue().equals("android.support.VERSION")) {
                      if (element.attribute("value")!= null)
                      element.attribute("value").setValue("25.3.1");
                  }
              }
          }
  //        if (supportVersion!= null && supportVersion.size() > 0){
  //            if (supportVersion.("name");
  //        }
  
          Map<String, String> replaceAttrs = mainManifestFileObject.getReplaceApplicationAttribute();
          List<String> removeAttrs = mainManifestFileObject.getRemoveApplicationAttribute();
  
          if (null != applicationElement) {
              // Remove the tools properties from the lib project
              List<Attribute> toRomoved = new ArrayList<Attribute>();
              for (Attribute attribute : applicationElement.attributes()) {
                  if (attribute.getName().equals("replace") || attribute.getName().equals("remove")) {
                      // applicationElement.remove(attribute);
                      // applicationElement.attributes().remove(attribute);
                      toRomoved.add(attribute);
                  }
              }
              if (toRomoved.size() > 0) {
                  for (Attribute attribute : toRomoved) {
                      applicationElement.remove(attribute);
                  }
              }
  
              updateApplicationElement(applicationElement, replaceAttrs, removeAttrs);
          }
  
          //Handle the packageName TODO
          String packageName = root.attributeValue("package");
          if (StringUtils.isEmpty(packageName)) {
              packageName = ManifestFileUtils.getPackage(orgManifestFile);
          }
  
          List<? extends Node> applicatNodes = root.selectNodes("//application");
          for (Node node : applicatNodes) {
              Element element = (Element)node;
              Attribute attribute = element.attribute("name");
              if (attribute != null) {
                  if (!attribute.getValue().startsWith(packageName)) {
                      attribute.setValue(packageName + attribute.getValue());
                  }
              }
          }
  
          fillFullClazzName(root, packageName, "activity");
          fillFullClazzName(root, packageName, "provider");
          fillFullClazzName(root, packageName, "receiver");
          fillFullClazzName(root, packageName, "service");
          if (incremental) {
              root.addNamespace("tools", "http://schemas.android.com/tools");
              List<? extends Node> nodes = root.selectNodes("//application/*");
              for (Node node : nodes) {
                  Element element = (Element)node;
                  element.addAttribute("tools:node", "replace");
              }
          }
  
          XmlHelper.saveDocument(document, modifyManifest);
      }
  
      private static void fillFullClazzName(Element root, String packageName, String type) {
          List<? extends Node> applicatNodes = root.selectNodes("//" + type);
          for (Node node : applicatNodes) {
              Element element = (Element)node;
              Attribute attribute = element.attribute("name");
              if (attribute != null) {
                  if (attribute.getValue().startsWith(".")) {
                      attribute.setValue(packageName + attribute.getValue());
                  }
              }
          }
      }
  
      /**
       * Update the Application node information
       *
       * @param element
       * @param replaceAttrs
       * @param removeAttrs
       */
      private static void updateApplicationElement(Element element, Map<String, String> replaceAttrs,
                                                   List<String> removeAttrs) {
          for (Map.Entry<String, String> entry : replaceAttrs.entrySet()) {
              String key = entry.getKey();
              key = StringUtils.substringAfter(key, ":");
              Attribute attribute = element.attribute(key);
              if (null != attribute) {
                  element.remove(attribute);
              }
          }
          for (String key : removeAttrs) {
              key = StringUtils.substringAfter(key, ":");
              Attribute attribute = element.attribute(key);
              if (null != attribute) {
                  element.remove(attribute);
              }
          }
      }
  
      /**
       * Update the attributes of element
       *
       * @param element
       * @param properties
       */
      private static void updateElement(Element element, Map<String, String> properties) {
          for (Map.Entry<String, String> entry : properties.entrySet()) {
              String key = entry.getKey();
              if (key.startsWith("tools:")) {
                  continue;
              }
              String keyNoPrefix = key;
              String[] values = StringUtils.split(key, ":");
              if (values.length > 1) {
                  keyNoPrefix = values[1];
              }
              if (null == entry.getValue()) {
                  continue;
              } else {
                  Attribute attribute = element.attribute(keyNoPrefix);
                  if (null != attribute) {
                      attribute.setValue(entry.getValue());
                  } else {
                      element.addAttribute(key, entry.getValue());
                  }
              }
          }
      }
  
      static Map<String, String> manifestMap = new HashMap<String, String>();
  
      public static String getPackage(File manifestFile) {
  
          String version = manifestMap.get(manifestFile.getAbsolutePath());
          if (null != version) {
              return version;
          }
  
          XPath xpath = AndroidXPathFactory.newXPath();
  
          try {
              version = xpath.evaluate("/manifest/@package", new InputSource(new FileInputStream(manifestFile)));
              manifestMap.put(manifestFile.getAbsolutePath(), version);
              return version;
          } catch (XPathExpressionException e) {
              // won't happen.
          } catch (FileNotFoundException e) {
              throw new RuntimeException(e);
          }
  
          return null;
      }
  
      public static String getApplicationName(File manifestFile) {
          XPath xpath = AndroidXPathFactory.newXPath();
          try {
              return xpath.evaluate("/manifest/application/@android:name",
                                    new InputSource(new FileInputStream(manifestFile)));
          } catch (XPathExpressionException e) {
              // won't happen.
          } catch (FileNotFoundException e) {
              throw new RuntimeException(e);
          }
  
          return null;
      }
  
      /**
       * Update the plug-in's minSdkVersion and targetSdkVersion
       *
       * @param androidManifestFile
       * @throws IOException
       * @throws DocumentException
       */
      public static String getVersionName(File androidManifestFile) throws IOException, DocumentException {
          SAXReader reader = new SAXReader();
          String versionName = "";
          if (androidManifestFile.exists()) {
              Document document = reader.read(androidManifestFile);// Read the XML file
              Element root = document.getRootElement();// Get the root node
              if ("manifest".equalsIgnoreCase(root.getName())) {
                  List<Attribute> attributes = root.attributes();
                  for (Attribute attr : attributes) {
                      if (StringUtils.equalsIgnoreCase(attr.getName(), "versionName")) {
                          versionName = attr.getValue();
                      }
                  }
              }
          }
          return versionName;
      }
  
      public static String getVersionCode(File androidManifestFile) throws IOException, DocumentException {
          SAXReader reader = new SAXReader();
          String versionCode = "";
          if (androidManifestFile.exists()) {
              Document document = reader.read(androidManifestFile);// Read the XML file
              Element root = document.getRootElement();// Get the root node
              if ("manifest".equalsIgnoreCase(root.getName())) {
                  List<Attribute> attributes = root.attributes();
                  for (Attribute attr : attributes) {
                      if (StringUtils.equalsIgnoreCase(attr.getName(), "versionCode")) {
                          versionCode = attr.getValue();
                      }
                  }
              }
          }
          return versionCode;
      }
  
      public static void removeProvider(File androidManifestFile) throws IOException, DocumentException {
          File backupFile = new File(androidManifestFile.getParentFile(), "AndroidManifest-backup.xml");
          FileUtils.deleteQuietly(backupFile);
          FileUtils.moveFile(androidManifestFile, backupFile);
  
          XMLWriter writer = null;// Declares the object that writes XML
          SAXReader reader = new SAXReader();
          OutputFormat format = OutputFormat.createPrettyPrint();
          format.setEncoding("UTF-8");// Sets the encoding format for the XML file
          FileOutputStream fos = new FileOutputStream(androidManifestFile);
  
          if (androidManifestFile.exists()) {
              try {
                  Document document = reader.read(backupFile);// Read the XML file
                  Element root = document.getRootElement();// Get the root node
                  List<? extends Node> nodes = root.selectNodes("//provider");
                  for (Node node : nodes) {
                      Element element = (Element)node;
                      String name = element.attributeValue("name");
                      logger.info("[Remove Provider]" + name);
                      element.getParent().remove(element);
                  }
                  writer = new XMLWriter(fos, format);
                  writer.write(document);
              } finally {
                  if (null != writer) {
                      writer.close();
                  }
                  IOUtils.closeQuietly(fos);
              }
          }
      }
  
      private static void removeProvider(Document document) throws IOException, DocumentException {
  
          Element root = document.getRootElement();// Get the root node
          List<? extends Node> nodes = root.selectNodes("//provider");
          for (Node node : nodes) {
              Element element = (Element)node;
              String name = element.attributeValue("name");
              logger.info("[Remove Provider]" + name);
              element.getParent().remove(element);
          }
      }
  
      private static void removeProcess(Document document) throws IOException, DocumentException {
  
          Element root = document.getRootElement();// Get the root node
          List<? extends Node> nodes = root.selectNodes("//*[@android:process]");
          for (Node node : nodes) {
              Element element = (Element)node;
              String process = element.attributeValue("process");
              logger.info("[Remove Element]" + element + process);
              element.remove(element.attribute("process"));
          }
      }
  
      public static void createPatchManifest(File mainManifest, File originalManifest, File destManifest)
          throws IOException, DocumentException {
  
          Document document = XmlHelper.readXml(mainManifest);
          Document baseDoc = XmlHelper.readXml(originalManifest);
  
          List<Node> newNodes = selectComponents(document.getRootElement());
          List<Node> baseNodes = selectComponents(baseDoc.getRootElement());
  
          Map<String, Node> baseNodeMap = new HashMap<>();
          for (Node node : baseNodes) {
              Element el = (Element)node;
              String key = el.attributeValue("process") + el.attributeValue("name");
              baseNodeMap.put(key, node);
          }
  
          Element applicationElement = document.getRootElement().element("application");
          applicationElement.clearContent();
          document.getRootElement().clearContent();
          document.getRootElement().add(applicationElement);
  
          for (Node node : newNodes) {
              Element el = (Element)node;
              String key = el.attributeValue("process") + el.attributeValue("name");
              if (!baseNodeMap.containsKey(key) && !el.attributeValue("name").startsWith(
                  AtlasProxy.ATLAS_PROXY_PACKAGE)) {
                  applicationElement.add(node);
              }
          }
  
          XmlHelper.saveDocument(document, destManifest);
      }
  
      private static List<Node> selectComponents(Element root) {
  
          String[] components = new String[] {"activity", "provider", "receiver", "service"};
  
          List<Node> nodes = new ArrayList<>();
          for (String component : components) {
              nodes.addAll(root.selectNodes("//" + component));
          }
  
          return nodes;
      }
  
      public static void main(String []args) throws DocumentException {
          File manifest = new File("/Users/lilong/.gradle/caches/transforms-1/files-1.1/recyclerview-v7-25.3.1.aar/1046cd92fdcfb77468417d61ed21e0db/AndroidManifest.xml");
          Document document = XmlHelper.readXml(manifest);
          Element element = document.getRootElement();
          Attribute attribute = element.attribute("meta-data");
      }
  }
  