/**
   * Copy from the copy method in StructUtil. Did not want to drag that code in. maybe this actually
   * should go to struct.
   *
   * @param from
   * @param to
   * @param excludes
   * @return
   * @throws Exception
   */
  public static <T extends struct> T xcopy(struct from, T to, String... excludes) throws Exception {
    Arrays.sort(excludes);
    for (Field f : from.fields()) {
      if (Arrays.binarySearch(excludes, f.getName()) >= 0) continue;

      Object o = f.get(from);
      if (o == null) continue;

      Field tof = to.getField(f.getName());
      if (tof != null)
        try {
          tof.set(to, Converter.cnv(tof.getGenericType(), o));
        } catch (Exception e) {
          System.out.println(
              "Failed to convert "
                  + f.getName()
                  + " from "
                  + from.getClass()
                  + " to "
                  + to.getClass()
                  + " value "
                  + o
                  + " exception "
                  + e);
        }
    }

    return to;
  }
  private int compare(Revision a, Revision b) {
    if (Arrays.equals(a._id, b._id)) return 0;

    Version va = getVersion(a);
    Version vb = getVersion(b);
    int n = va.compareTo(vb);
    if (n != 0) return n;

    if (a.created != b.created) return a.created > b.created ? 1 : -1;

    for (int i = 0; i < a._id.length; i++)
      if (a._id[i] != b._id[i]) return a._id[i] > b._id[i] ? 1 : -1;

    return 0;
  }
  public String verify(JarFile jar, String... algorithms) throws IOException {
    if (algorithms == null || algorithms.length == 0) algorithms = new String[] {"MD5", "SHA"};
    else if (algorithms.length == 1 && algorithms[0].equals("-")) return null;

    try {
      Manifest m = jar.getManifest();
      if (m.getEntries().isEmpty()) return "No name sections";

      for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
        JarEntry je = e.nextElement();
        if (MANIFEST_ENTRY.matcher(je.getName()).matches()) continue;

        Attributes nameSection = m.getAttributes(je.getName());
        if (nameSection == null) return "No name section for " + je.getName();

        for (String algorithm : algorithms) {
          try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            String expected = nameSection.getValue(algorithm + "-Digest");
            if (expected != null) {
              byte digest[] = Base64.decodeBase64(expected);
              copy(jar.getInputStream(je), md);
              if (!Arrays.equals(digest, md.digest()))
                return "Invalid digest for "
                    + je.getName()
                    + ", "
                    + expected
                    + " != "
                    + Base64.encodeBase64(md.digest());
            } else reporter.error("could not find digest for " + algorithm + "-Digest");
          } catch (NoSuchAlgorithmException nsae) {
            return "Missing digest algorithm " + algorithm;
          }
        }
      }
    } catch (Exception e) {
      return "Failed to verify due to exception: " + e.getMessage();
    }
    return null;
  }