class JarWriter {

  public Path rewrite(Path _todir) throws JarAnalysisException {
    // Target dir
    Path dir = _todir;

    // Callback logic
    Matcher matcher = null;

    // Loop all entries of the old JAR
    JarEntry old_entry = null, new_entry = null;

    try {
      if (dir == null) dir = java.nio.file.Files.createTempDirectory("rewritten_jar_");
      this.rewrittenFile =
          Paths.get(dir.toString(), this.getRewriteJarFileName().toString()).toFile();

      if (this.rewrittenFile.exists()) {
        JarWriter.log.info(
            "The target [" + this.rewrittenFile + "] already exists, skip rewriting");
      } else {
        final FileOutputStream fos = new FileOutputStream(this.rewrittenFile);
        final JarOutputStream jos = new JarOutputStream(fos, this.createModifiedManifest());
        InputStream is = null;
        byte[] bytes = new byte[1024];
        int bytes_read = 0;

        final Enumeration<JarEntry> en = this.originalJar.entries();
        String class_name = null;
        JavaId jid = null;
        Set<JavaId> jids = new HashSet<JavaId>();

        // Remember all JAR entries written to the new JAR, so that we do not create duplicate
        // entries
        // Example of a duplicate entry: Location.class in xmlbeans-2.6.0.jar
        Set<String> written_jar_entries = new HashSet<String>();

        while (en.hasMoreElements()) {
          old_entry = en.nextElement();

          // The input stream used for writing the entries
          is = null;

          // Check whether we already write an entry with this name
          if (written_jar_entries.contains(old_entry.getName())) continue;

          // Ignore the original manifest (we built a new one with certain vulas-specific
          // attributes)
          if (old_entry.getName().equals("META-INF/MANIFEST.MF")) continue;

          // Ignore signature related files (->
          // http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Signed_JAR_File)
          if (old_entry.getName().startsWith("META-INF/")
              && (old_entry.getName().toLowerCase().endsWith(".sf")
                  || old_entry.getName().toLowerCase().endsWith(".dsa")
                  || old_entry.getName().toLowerCase().endsWith(".rsa"))) continue;

          // Loop registered JarEntryWriters to see if any matches (take the input stream from the
          // first match)
          for (Map.Entry<Pattern, JarEntryWriter> e : this.callbacks.entrySet()) {
            matcher = e.getKey().matcher(old_entry.getName());
            if (matcher.matches()) {
              is = e.getValue().getInputStream(e.getKey().toString(), old_entry);
            }
          }

          // If null, take the original file
          if (is == null) is = this.originalJar.getInputStream(old_entry);

          // Debug information regarding specific attributes
          if (old_entry.getAttributes() != null)
            JarWriter.log.debug(
                this.toString() + ": Entry [" + old_entry.getName() + "] has specific attributes");

          // Write the entry to the modified JAR
          new_entry = new JarEntry(old_entry.getName());
          jos.putNextEntry(new_entry);
          while ((bytes_read = is.read(bytes)) != -1) jos.write(bytes, 0, bytes_read);

          // is.close();
          jos.closeEntry();

          // Remember we wrote it
          written_jar_entries.add(new_entry.getName());
        }

        // Add additional files
        for (Map.Entry<String, Path> e : this.additionalFiles.entrySet()) {
          if (e.getValue().toFile().exists()) {
            new_entry = new JarEntry(e.getKey());
            jos.putNextEntry(new_entry);
            is = new FileInputStream(e.getValue().toFile());
            while ((bytes_read = is.read(bytes)) != -1) jos.write(bytes, 0, bytes_read);
            is.close();
            jos.closeEntry();
          }
        }

        jos.flush();
        jos.close();
        this.originalJar.close();

        //
        old_entry = null;
        JarWriter.log.info(
            "[" + this.originalJar.getName() + "] rewritten to [" + this.rewrittenFile + "]");
      }
    } catch (Exception ioe) {
      if (old_entry != null)
        throw new JarAnalysisException(
            "Error while writing JAR entry ["
                + old_entry.getName()
                + "] to modified JAR ["
                + this.rewrittenFile
                + "]: "
                + ioe.getMessage(),
            ioe);
      else
        throw new JarAnalysisException(
            "Error while writing modified JAR: " + ioe.getMessage(), ioe);
    }
    return this.rewrittenFile.toPath();
  }

  public  JarWriter(Path _jar);

}