/**
   * Gets maps that are not installed, but may be downloaded.
   *
   * @return set of all maps available for download
   */
  public static Set<AutoRefMap> getRemoteMaps() {
    // check the cache first, we might want to just use the cached value
    long time = ManagementFactory.getRuntimeMXBean().getUptime() - _cachedRemoteMapsTime;
    if (_cachedRemoteMaps != null && time < AutoRefMap.REMOTE_MAP_CACHE_LENGTH)
      return _cachedRemoteMaps;

    Set<AutoRefMap> maps = Sets.newHashSet();
    String repo = AutoRefMatch.getMapRepo();

    try {
      Map<String, String> params = Maps.newHashMap();
      params.put("prefix", "maps/");

      for (; ; ) {
        String url = String.format("%s?%s", repo, QueryUtil.prepareParams(params));
        Element listing = new SAXBuilder().build(new URL(url)).getRootElement();
        assert "ListBucketResult".equals(listing.getName()) : "Unexpected response";
        Namespace ns = listing.getNamespace();

        String lastkey = null;
        for (Element entry : listing.getChildren("Contents", ns)) {
          lastkey = entry.getChildTextTrim("Key", ns);
          if (!lastkey.endsWith(".zip")) continue;

          String[] keyparts = lastkey.split("/");
          String mapfile = keyparts[keyparts.length - 1];

          String mapslug = mapfile.substring(0, mapfile.length() - 4);
          String slugparts[] = mapslug.split("-v");

          if (slugparts.length < 2) {
            AutoReferee.log("Invalid map filename: " + mapfile, Level.WARNING);
            AutoReferee.log("Map files should be of the form \"MapName-vX.X.zip\"", Level.WARNING);
          } else {
            String etag = entry.getChildTextTrim("ETag", ns);
            maps.add(
                new AutoRefMap(
                    slugparts[0], slugparts[1], lastkey, etag.substring(1, etag.length() - 1)));
          }
        }

        // stop looping if the result says that it hasn't been truncated (no more results)
        if (!Boolean.parseBoolean(listing.getChildText("IsTruncated", ns))) break;

        // use the last key as a marker for the next pass
        if (lastkey != null) params.put("marker", lastkey);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } catch (JDOMException e) {
      e.printStackTrace();
    }

    _cachedRemoteMapsTime = ManagementFactory.getRuntimeMXBean().getUptime();
    return _cachedRemoteMaps = maps;
  }
  private void download() throws IOException {
    // mark the file as downloading
    this.zip = AutoRefMap.DOWNLOADING;

    String bparts[] = filename.split("/"), basename = bparts[bparts.length - 1];
    File zip = new File(AutoRefMap.getMapLibrary(), basename);
    FileUtils.copyURLToFile(new URL(AutoRefMatch.getMapRepo() + filename), zip);

    // if the md5s match, return the zip
    String md5comp = DigestUtils.md5Hex(new FileInputStream(zip));
    if (md5comp.equalsIgnoreCase(md5sum)) {
      this.zip = zip;
      return;
    }

    // if the md5sum did not match, quit here
    zip.delete();
    throw new IOException("MD5 Mismatch: " + md5comp + " != " + md5sum);
  }