blob: b3d9a1af19eaf9b84555bb20b6af4df0d51f9df2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import org.apache.commons.bcel6.Constants;
import org.apache.commons.bcel6.classfile.ClassParser;
import org.apache.commons.bcel6.classfile.Constant;
import org.apache.commons.bcel6.classfile.ConstantClass;
import org.apache.commons.bcel6.classfile.ConstantPool;
import org.apache.commons.bcel6.classfile.ConstantUtf8;
import org.apache.commons.bcel6.classfile.JavaClass;
import org.apache.commons.bcel6.util.ClassPath;
/**
* Package the client. Creates a jar file in the current directory
* that contains a minimal set of classes needed to run the client.
*
* Use BCEL to extract class names and read/write classes
*
* @author First Hop Ltd / Torsten Rueger
*/
public class Package {
/**
* The name of the resulting jar is Client.jar
*/
static String defaultJar = "Client.jar";
/*
* See usage() for arguments. Create an instance and run that
*(just so not all members have to be static)
*/
static void main(String args[]) {
Package instance = new Package();
try {
instance.go(args);
} catch (Exception e) {
e.printStackTrace();
instance.usage();
}
}
/**
* We use a "default ClassPath object which uses the environments
* CLASSPATH
*/
ClassPath classPath = ClassPath.SYSTEM_CLASS_PATH;
/**
* A map for all Classes, the ones we're going to package.
* Store class name against the JavaClass. From the JavaClass
* we get the bytes to create the jar.
*/
Map<String, JavaClass> allClasses = new TreeMap<String, JavaClass>();
/**
* We start at the root classes, put them in here, then go through
* this list, putting dependent classes in here and from there
* into allClasses. Store class names against class names of their dependents
*/
TreeMap<String, String> dependents = new TreeMap<String, String>();
/**
* Collect all classes that could not be found in the classpath.
* Store class names against class names of their dependents
*/
TreeMap<String, String> notFound = new TreeMap<String, String>();
/**
* See wheather we print the classes that were not found (default = false)
*/
boolean showNotFound = false;
/**
* Remember wheather to print allClasses at the end (default = false)
*/
boolean printClasses = false;
/**
* Wheather we log classes during processing (default = false)
*/
boolean log = false;
public void usage() {
System.out.println(" This program packages classes and all their dependents");
System.out.println(" into one jar. Give all starting classes (your main)");
System.out.println(" on the command line. Use / as separator, the .class is");
System.out.println(" optional. We use the environments CLASSPATH to resolve");
System.out.println(" classes. Anything but java.* packages are packaged.");
System.out.println(" If you use Class.forName (or similar), be sure to");
System.out.println(" include the classes that you load dynamically on the");
System.out.println(" command line.\n");
System.out.println(" These options are recognized:");
System.out.println(" -e -error Show errors, meaning classes that could not ");
System.out.println(" resolved + the classes that referenced them.");
System.out.println(" -l -log Show classes as they are processed. This will");
System.out.println(" include doubles, java classes and is difficult to");
System.out.println(" read. I use it as a sort of progress monitor");
System.out.println(" -s -show Prints all the classes that were packaged");
System.out.println(" in alphabetical order, which is ordered by package");
System.out.println(" for the most part.");
}
/**
* the main of this class
*/
void go(String[] args) throws IOException {
JavaClass clazz;
// sort the options
for (String arg : args) {
if (arg.startsWith("-e")) {
showNotFound = true;
continue;
}
if (arg.startsWith("-s")) {
printClasses = true;
continue;
}
if (arg.startsWith("-l")) {
log = true;
continue;
}
String clName = arg;
if (clName.endsWith(".class")) {
clName = clName.substring(0, clName.length() - 6);
}
clName = clName.replace('.', '/');
clazz = new ClassParser(classPath.getInputStream(clName), clName).parse();
// here we create the root set of classes to process
addDependents(clazz);
System.out.println("Packaging for class: " + clName);
}
if (dependents.isEmpty()) {
usage();
return;
}
System.out.println("Creating jar file: " + defaultJar);
// starting processing: Grab from the dependents list an add back to it
// and the allClasses list. see addDependents
while (!dependents.isEmpty()) {
String name = dependents.firstKey();
String from = dependents.remove(name);
if (allClasses.get(name) == null) {
try {
InputStream is = classPath.getInputStream(name);
clazz = new ClassParser(is, name).parse();
addDependents(clazz);
} catch (IOException e) {
//System.err.println("Error, class not found " + name );
notFound.put(name, from);
}
}
}
if (printClasses) { // if wanted show all classes
printAllClasses();
}
// create the jar
JarOutputStream jarFile = new JarOutputStream(new FileOutputStream(defaultJar));
jarFile.setLevel(5); // use compression
int written = 0;
for (String name : allClasses.keySet()) { // add entries for every class
JavaClass claz = allClasses.get(name);
ZipEntry zipEntry = new ZipEntry(name + ".class");
byte[] bytes = claz.getBytes();
int length = bytes.length;
jarFile.putNextEntry(zipEntry);
jarFile.write(bytes, 0, length);
written += length; // for logging
}
jarFile.close();
System.err.println("The jar file contains " + allClasses.size()
+ " classes and contains " + written + " bytes");
if (!notFound.isEmpty()) {
System.err.println(notFound.size() + " classes could not be found");
if (showNotFound) { // if wanted show the actual classes that we not found
while (!notFound.isEmpty()) {
String name = notFound.firstKey();
System.err.println(name + " (" + notFound.remove(name) + ")");
}
} else {
System.err.println("Use '-e' option to view classes that were not found");
}
}
}
/**
* Print all classes that were packaged. Sort alphabetically for better
* overview. Enabled by -s option
*/
void printAllClasses() {
List<String> names = new ArrayList<String>(allClasses.keySet());
Collections.sort(names);
for (int i = 0; i < names.size(); i++) {
String cl = names.get(i);
System.err.println(cl);
}
}
/**
* Add this class to allClasses. Then go through all its dependents
* and add them to the dependents list if they are not in allClasses
*/
void addDependents(JavaClass clazz) throws IOException {
String name = clazz.getClassName().replace('.', '/');
allClasses.put(name, clazz);
ConstantPool pool = clazz.getConstantPool();
for (int i = 1; i < pool.getLength(); i++) {
Constant cons = pool.getConstant(i);
//System.out.println("("+i+") " + cons );
if (cons != null && cons.getTag() == Constants.CONSTANT_Class) {
int idx = ((ConstantClass) pool.getConstant(i)).getNameIndex();
String clas = ((ConstantUtf8) pool.getConstant(idx)).getBytes();
addClassString(clas, name);
}
}
}
/**
* add given class to dependents (from is where its dependent from)
* some fiddeling to be done because of array class notation
*/
void addClassString(String clas, String from) throws IOException {
if (log) {
System.out.println("processing: " + clas + " referenced by " + from);
}
// must check if it's an arrary (start with "[")
if (clas.startsWith("[")) {
if (clas.length() == 2) {
// it's an array of built in type, ignore
return;
}
if ('L' == clas.charAt(1)) {
// it's an array of objects, the class name is between [L and ;
// like [Ljava/lang/Object;
addClassString(clas.substring(2, clas.length() - 1), from);
return;
}
if ('[' == clas.charAt(1)) {
// it's an array of arrays, call recursive
addClassString(clas.substring(1), from);
return;
}
throw new IOException("Can't recognize class name =" + clas);
}
if (!clas.startsWith("java/") && allClasses.get(clas) == null) {
dependents.put(clas, from);
// System.out.println(" yes" );
} else {
// System.out.println(" no" );
}
}
}