/**
   * Lists remote packages available for install using {@link UpdaterData#updateOrInstallAll_NoGUI}.
   *
   * @param includeAll True to list and install all packages, including obsolete ones.
   * @param extendedOutput True to display more details on each package.
   */
  public void listRemotePackages_NoGUI(boolean includeAll, boolean extendedOutput) {

    List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);

    mSdkLog.info("Packages available for installation or update: %1$d\n", archives.size());

    int index = 1;
    for (ArchiveInfo ai : archives) {
      Archive a = ai.getNewArchive();
      if (a != null) {
        Package p = a.getParentPackage();
        if (p != null) {
          if (extendedOutput) {
            mSdkLog.info("----------\n");
            mSdkLog.info("id: %1$d or \"%2$s\"\n", index, p.installId());
            mSdkLog.info(
                "     Type: %1$s\n",
                p.getClass()
                    .getSimpleName()
                    .replaceAll("Package", "")); // $NON-NLS-1$ //$NON-NLS-2$
            String desc = LineUtil.reformatLine("     Desc: %s\n", p.getLongDescription());
            mSdkLog.info("%s", desc); // $NON-NLS-1$
          } else {
            mSdkLog.info("%1$ 4d- %2$s\n", index, p.getShortDescription());
          }
          index++;
        }
      }
    }
  }
  /**
   * Tries to update all the *existing* local packages. This version is intended to run without a
   * GUI and only outputs to the current {@link ILogger}.
   *
   * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()} or
   *     package indexes to limit the packages we can update or install. A null or empty list means
   *     to update everything possible.
   * @param includeAll True to list and install all packages, including obsolete ones.
   * @param dryMode True to check what would be updated/installed but do not actually download or
   *     install anything.
   * @return A list of archives that have been installed. Can be null if nothing was done.
   */
  public List<Archive> updateOrInstallAll_NoGUI(
      Collection<String> pkgFilter, boolean includeAll, boolean dryMode) {

    List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);

    // Filter the selected archives to only keep the ones matching the filter
    if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
      // Map filter types to an SdkRepository Package type,
      // e.g. create a map "platform" => PlatformPackage.class
      HashMap<String, Class<? extends Package>> pkgMap =
          new HashMap<String, Class<? extends Package>>();

      mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
      mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);

      // Prepare a map install-id => package instance
      HashMap<String, Package> installIdMap = new HashMap<String, Package>();
      for (ArchiveInfo ai : archives) {
        Archive a = ai.getNewArchive();
        if (a != null) {
          Package p = a.getParentPackage();
          if (p != null) {
            String id = p.installId();
            if (id != null && id.length() > 0 && !installIdMap.containsKey(id)) {
              installIdMap.put(id, p);
            }
          }
        }
      }

      // Now intersect this with the pkgFilter requested by the user, in order to
      // only keep the classes that the user wants to install.
      // We also create a set with the package indices requested by the user
      // and a set of install-ids requested by the user.

      HashSet<Class<? extends Package>> userFilteredClasses =
          new HashSet<Class<? extends Package>>();
      SparseIntArray userFilteredIndices = new SparseIntArray();
      Set<String> userFilteredInstallIds = new HashSet<String>();

      for (String type : pkgFilter) {
        if (installIdMap.containsKey(type)) {
          userFilteredInstallIds.add(type);

        } else if (type.replaceAll("[0-9]+", "").length() == 0) { // $NON-NLS-1$ //$NON-NLS-2$
          // An all-digit number is a package index requested by the user.
          int index = Integer.parseInt(type);
          userFilteredIndices.put(index, index);

        } else if (pkgMap.containsKey(type)) {
          userFilteredClasses.add(pkgMap.get(type));

        } else {
          // This should not happen unless there's a mismatch in the package map.
          mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
        }
      }

      // we don't need the maps anymore
      pkgMap = null;
      installIdMap = null;

      // Now filter the remote archives list to keep:
      // - any package which class matches userFilteredClasses
      // - any package index which matches userFilteredIndices
      // - any package install id which matches userFilteredInstallIds

      int index = 1;
      for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
        boolean keep = false;
        ArchiveInfo ai = it.next();
        Archive a = ai.getNewArchive();
        if (a != null) {
          Package p = a.getParentPackage();
          if (p != null) {
            if (userFilteredInstallIds.contains(p.installId())
                || userFilteredClasses.contains(p.getClass())
                || userFilteredIndices.get(index) > 0) {
              keep = true;
            }

            index++;
          }
        }

        if (!keep) {
          it.remove();
        }
      }

      if (archives.size() == 0) {
        mSdkLog.info(
            LineUtil.reflowLine(
                "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n"));
        return null;
      }
    }

    if (archives != null && archives.size() > 0) {
      if (dryMode) {
        mSdkLog.info("Packages selected for install:\n");
        for (ArchiveInfo ai : archives) {
          Archive a = ai.getNewArchive();
          if (a != null) {
            Package p = a.getParentPackage();
            if (p != null) {
              mSdkLog.info("- %1$s\n", p.getShortDescription());
            }
          }
        }
        mSdkLog.info("\nDry mode is on so nothing is actually being installed.\n");
      } else {
        return installArchives(archives, NO_TOOLS_MSG);
      }
    } else {
      mSdkLog.info("There is nothing to install or update.\n");
    }

    return null;
  }