code: package org.gridkit.jvmtool.bstub;
  
  import java.io.BufferedReader;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.nio.charset.Charset;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.TreeSet;
  import java.util.jar.JarFile;
  import java.util.jar.Manifest;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  import java.util.zip.ZipOutputStream;
  
  public class JarBuilderTool {
      
      private Manifest mf = new Manifest();
      private List<String> packages = new ArrayList<String>();
      private List<String> services = new ArrayList<String>();
  
      private Set<String> pathEntries = new HashSet<String>();
      
      public Manifest getManifest() {
          return mf;
      }
      
      public void addPackage(String pkg) {
          packages.add(pkg.replace('.', '/'));
      }
      
      public void addService(String service) {
          services.add(service);
      }
      
      public byte[] collectJar() throws IOException {
          Manifest manifest = mf;
          ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
          ZipOutputStream jarOut = new ZipOutputStream(bos);
          if (manifest != null) {
              ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
              e.setTime(0l); // this to ensure equal hash for equal content
              jarOut.putNextEntry(e);
              manifest.write(jarOut);
              jarOut.closeEntry();            
          }
          for(String srv: services) {
              processService(srv, jarOut);
          }
          for(String pkg: packages) {
              processPath(pkg, jarOut);
          }
          pathEntries.clear();
          jarOut.close();
          byte[] jarFile = bos.toByteArray();
          return jarFile;
      }
      
      private void processService(String srv, ZipOutputStream jarOut) throws IOException {
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          Set<String> imps = new TreeSet<String>();
          
          String sd = "META-INF/services/" + srv;
          Enumeration<URL> en = cl.getResources(sd);
          while(en.hasMoreElements()) {
              URL url = en.nextElement();
              Collection<String> c = toLines(url.openStream());
              imps.addAll(c);
          }
  
          StringBuilder sb = new StringBuilder();
          for(String s: imps) {
              s = s.trim();
              if (!s.isEmpty()) {
                  sb.append(s).append('\n');
              }
          }
  
          ZipEntry entry = new ZipEntry(sd);
          entry.setTime(0); // this is to facilitate content cache
          if (!pathEntries.contains(entry.getName())) {
              pathEntries.add(entry.getName());
              jarOut.putNextEntry(entry);
              jarOut.write(sb.toString().getBytes(Charset.forName("UTF8")));
              jarOut.closeEntry();                            
          }        
      }
  
      private void processPath(String path, ZipOutputStream jarOut) throws MalformedURLException, IOException {
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          String basePackage = path;
          Enumeration<URL> en = cl.getResources(path);
          while(en.hasMoreElements()) {
              URL url = en.nextElement(); 
              String urlp = url.toExternalForm();
              if (urlp.indexOf('?') > 0) {
                  urlp = urlp.substring(0, urlp.indexOf('?'));
              }
              addFiles(jarOut, basePackage, urlp);                    
          }
      }
  
  //    private static byte[] jarFiles(String path) throws IOException {
  //        ByteArrayOutputStream bos = new ByteArrayOutputStream();
  //        JarOutputStream jarOut = new JarOutputStream(bos);
  //        int size = addFiles(jarOut, "", new File(path));
  //        if (size == 0) {
  //            // no files in folder
  //            return null;
  //        }
  //        jarOut.close();
  //        return bos.toByteArray();
  //    }
  //
  //    private static int addFiles(JarOutputStream jarOut, String base, File path) throws IOException {
  //        int count = 0;
  //        for(File file : path.listFiles()) {
  //            if (file.isDirectory()) {
  //                final String dirName = base + file.getName() + "/";
  //
  //                JarEntry entry = new JarEntry(dirName);
  //                entry.setTime(0l);// this to ensure equal hash for equal content
  //                jarOut.putNextEntry(entry);
  //                jarOut.closeEntry();
  //                count += addFiles(jarOut, dirName, file);
  //            }
  //            else {
  //                JarEntry entry = new JarEntry(base + file.getName());
  //                entry.setTime(file.lastModified());
  //                jarOut.putNextEntry(entry);
  //                copyStream(new FileInputStream(file), jarOut);
  //                jarOut.closeEntry();
  //                ++count; 
  //            }
  //        }
  //        return count;
  //    }
  
      private void addFiles(ZipOutputStream jarOut, String basePackage, String baseUrl) throws IOException, MalformedURLException {
          if (baseUrl.startsWith("jar:")) {
              int n = baseUrl.lastIndexOf("!");
              if (n < 0) {
                  throw new IllegalArgumentException("Unexpected classpath URL: " + baseUrl);
              }
              String fileUrl = baseUrl.substring(4, n);
              InputStream is = new URL(fileUrl).openStream();
              ZipInputStream zis = new ZipInputStream(is);
              while(true) {
                  ZipEntry ze = zis.getNextEntry();
                  if (ze != null) {
                      if (matchPath(ze.getName(), basePackage)) {
                          ZipEntry entry = new ZipEntry(ze.getName());
                          entry.setTime(0); // this is to facilitate content cache
                          if (!pathEntries.contains(entry.getName())) {
                              pathEntries.add(entry.getName());
                              jarOut.putNextEntry(entry);
                              copyStreamNoClose(zis, jarOut);
                              jarOut.closeEntry();                            
                          }
                      }
                      zis.closeEntry();
                  }
                  else {
                      break;
                  }               
              }
          }
          else {
              InputStream is = new URL(baseUrl).openStream();
              for(String line: toLines(is)) {
                  String fpath = baseUrl + "/" + line;
                  String jpath = basePackage + "/" + line;
                  ZipEntry entry = new ZipEntry(jpath);
                  entry.setTime(0); // this is to facilitate content cache            
                  if (!pathEntries.contains(entry.getName())) {
                      jarOut.closeEntry();                            
                      jarOut.putNextEntry(entry);
                      copyStream(new URL(fpath).openStream(), jarOut);
                      jarOut.closeEntry();
                  }
              }
          }
      }
      
      private boolean matchPath(String path, String basePath) {
          return path.startsWith(basePath) && path.lastIndexOf('/') <= basePath.length();
      }
  
      private static void copyStream(InputStream in, OutputStream out) throws IOException {
          try {
              byte[] buf = new byte[1 << 12];
              while(true) {
                  int n = in.read(buf);
                  if(n >= 0) {
                      out.write(buf, 0, n);
                  }
                  else {
                      break;
                  }
              }
          } finally {
              try {
                  in.close();
              }
              catch(Exception e) {
                  // ignore
              }
          }
      }       
      
      private static Collection<String> toLines(InputStream is) throws IOException {
          try {
              BufferedReader reader = new BufferedReader(new InputStreamReader(is));
              List<String> result = new ArrayList<String>();
              while(true) {
                  String line = reader.readLine();
                  if (line == null) {
                      break;
                  }
                  result.add(line);
              }
              return result;
          }
          finally {
              try {
                  is.close();
              }
              catch(Exception e) {
                  // ignore
              }
          }
      }
      
      private static void copyStreamNoClose(InputStream in, OutputStream out) throws IOException {
          boolean doClose = true;
          try {
              byte[] buf = new byte[1 << 12];
              while(true) {
                  int n = in.read(buf);
                  if(n >= 0) {
                      out.write(buf, 0, n);
                  }
                  else {
                      break;
                  }
              }
              doClose = false;
              
          } finally {
              if (doClose) {
                  // close if there were exception thrown
                  try {
                      in.close();
                  }
                  catch(Exception e) {
                      // ignore
                  }
              }
          }
      }   
  }
  