/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'Shaven Puppy' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.shavenpuppy.jglib.tools;
import java.io.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.*;
/**
* The Jar Masher takes as its input an rt.jar and a list of loaded classes from a -verbose:class dump. It then outputs a new rt.jar
* which contains only those classes referenced in the class dump.
*/
public class JarMasher {
/**
* Usage: java jarMasher source=rt.jar dest=new-rt.jar classlist=classes.txt [verbose=true] [overwrite=false]
* @param args
*/
public static void main(String[] args) {
String source = parse(args, "source");
String dest = parse(args, "dest");
String classList = parse(args, "classlist");
String verbose = parse(args, "verbose", "true");
String overwrite = parse(args, "overwrite", "false");
String includeList = parse(args, "include");
if (source == null || dest == null || classList == null) {
printUsage();
System.exit(-1);
return;
}
try {
new JarMasher(source, dest, classList, includeList, Boolean.parseBoolean(verbose), Boolean.parseBoolean(overwrite)).process();
} catch (IOException e) {
e.printStackTrace(System.err);
System.exit(-1);
}
}
private static void printUsage() {
System.err.println("Usage:\n\tjava jarMasher source=<path> dest=<path> classlist=<path> [includelist=[<path>]] [verbose=true] [overwrite=false]");
}
private static String parse(String[] args, String key) {
return parse(args, key, null);
}
private static String parse(String[] args, String match, String default_) {
for (String arg : args) {
int idx = arg.indexOf('=');
if (idx > 0) {
String key = arg.substring(0, idx);
String value = arg.substring(idx + 1);
if (key.equalsIgnoreCase(match)) {
if (value.startsWith("\"") && value.endsWith("\"")) {
value = value.substring(1, value.length() - 2);
}
return value;
}
}
}
return default_;
}
private final File sourceFile;
private final File destFile;
private final File classListFile;
private final File includeListFile;
private final boolean verbose;
private final boolean overwrite;
public JarMasher(String source, String dest, String classList, String includeList, boolean verbose, boolean overwrite) {
this.sourceFile = new File(source);
this.destFile = new File(dest);
this.classListFile = new File(classList);
this.includeListFile = includeList == null ? null : new File(includeList);
this.verbose = verbose;
this.overwrite = overwrite;
}
public void process() throws IOException {
// Check files exist
if (!sourceFile.exists()) {
throw new RuntimeException("source file does not exist");
}
if (!classListFile.exists()) {
throw new RuntimeException("classlist file does not exist");
}
if (sourceFile.equals(destFile)) {
throw new RuntimeException("source file must not be the same as dest file");
}
if (destFile.exists() && !overwrite) {
throw new RuntimeException("dest file exists and overwrite=false; specify overwrite=true");
}
// Read the classlist file first
Set<String> classesUsed = loadClassList();
// Load include list, if present
Set<String> packagesIncluded = loadIncludeList();
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);
ZipOutputStream zos = new ZipOutputStream(bos);
// Find out what's in the source jar file
JarFile sourceJarFile = new JarFile(sourceFile, false, ZipFile.OPEN_READ);
for (Enumeration<JarEntry> entries = sourceJarFile.entries(); entries.hasMoreElements(); ) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
boolean add = false;
if (entryName.endsWith(".class")) {
String className = entryName.substring(0, entryName.length() - 6).replace('/', '.');
if (!classesUsed.contains(className)) {
// Check if it's in an included package
int idx = className.lastIndexOf('.');
while (idx != -1) {
String packageName = className.substring(0, idx);
if (packagesIncluded.contains(packageName)) {
add = true;
break;
} else {
idx = packageName.lastIndexOf('.');
}
}
} else {
add = true;
}
} else {
add = true;
}
if (add) {
System.out.println("Adding "+entry);
// Read the zip entry out of the source and stick it in the dest
InputStream src = sourceJarFile.getInputStream(entry);
ZipEntry out = new ZipEntry(entry);
zos.putNextEntry(out);
byte[] buf = new byte[(int) entry.getSize()];
int read = -1;
while ((read = src.read(buf)) != -1) {
zos.write(buf, 0, read);
}
zos.closeEntry();
}
}
zos.flush();
zos.close();
}
private Set<String> loadClassList() throws IOException {
FileReader fr = new FileReader(classListFile);
BufferedReader br = new BufferedReader(fr);
TreeSet<String> ret = new TreeSet<String>();
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("[Loaded ")) {
int idx = line.indexOf(' ', 8);
if (idx != -1) {
String className = line.substring(8, idx);
ret.add(className);
}
}
}
return ret;
}
private Set<String> loadIncludeList() throws IOException {
if (includeListFile == null || !includeListFile.exists()) {
return new HashSet<String>(0);
}
FileReader fr = new FileReader(includeListFile);
BufferedReader br = new BufferedReader(fr);
TreeSet<String> ret = new TreeSet<String>();
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.startsWith("//")) {
continue;
}
ret.add(line);
}
return ret;
}
}