package org.rzo.builder; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemManager; import org.apache.commons.vfs2.VFS; /** * * compress multiple jar files into a single jar file * * 1. run a program with -verbose:class, so that all required classes are loaded and logged * 2. write log to a file * 3. call java JarBuilder log-file newjar * * JarBuilder will build a jar with the files used by the application * */ public class JarBuilder { // multimap: assignment of jar files to class files used in the application Map<String, Set<String>> _jar2class = new HashMap<String, Set<String>>(); // populate jar2class private void parseLog(String file) throws IOException { InputStream in = new FileInputStream(file); // -verbose:class logs have the pattern [Loaded <class> from file:/<jar or folder>] Pattern p = Pattern.compile("\\[Loaded\\s(.*)\\s+from file\\:/(.*)\\]"); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = null; try { while ((line = reader.readLine()) != null) { Matcher m = p.matcher(line); if (m.find()) { String clazz = m.group(1); String jar = m.group(2); // TODO do not add java classes if (!jar.endsWith("/rt.jar")) addClazz(jar, clazz); } } } catch (IOException e) { e.printStackTrace(); } } // add an entry to the multimap private void addClazz(String jar, String clazz) { Set<String> clazzes = _jar2class.get(jar); System.out.println("add "+jar+"!"+clazz); if (clazzes == null) { clazzes = new HashSet<String>(); _jar2class.put(jar, clazzes); } clazzes.add(clazz); } // add a file to the destination jar private void addFile(FileObject f, String name, ZipOutputStream out) throws IOException { InputStream in = f.getContent().getInputStream(); out.putNextEntry(new ZipEntry(name)); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } // Complete the entry out.closeEntry(); in.close(); } // build the jar // VFS currently does not support building zip files -> use java's ZipOutputStream private void buildJar(String newJar) throws IOException { ZipOutputStream out = new ZipOutputStream(new FileOutputStream(newJar )); FileSystemManager fsManager = VFS.getManager(); for (String jar : _jar2class.keySet()) { FileObject jarFile; if (jar.endsWith(".jar")) jarFile = fsManager.resolveFile( "jar:"+jar ); else jarFile = fsManager.resolveFile( jar ); for (String file : _jar2class.get(jar)) { file = file.replaceAll("\\.", "/"); file += ".class"; FileObject f = fsManager.resolveFile(jarFile, file); if (f.exists()) addFile(f, file, out); else System.out.println("file not found "+f); } } out.close(); } public static void main(String[] args) throws Exception { String logFile = args[0]; String newJar = args[1]; new File(newJar).delete(); JarBuilder b = new JarBuilder(); b.parseLog(logFile); b.buildJar(newJar); } }