blob: 63988ee0e6dfca74962f1128036a7683f6b0a3f9 [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
*
* https://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.Map.Entry;
import java.util.TreeMap;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.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
*/
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(final String args[]) {
final Package instance = new Package();
try {
instance.go(args);
} catch (final 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<>();
/**
* 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<>();
/**
* 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<>();
/**
* 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;
/**
* add given class to dependents (from is where its dependent from) some fiddeling to be done because of array class
* notation
*/
void addClassString(final String clas, final 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 IllegalArgumentException("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" );
}
}
/**
* 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(final JavaClass clazz) throws IOException {
final String name = Utility.packageToPath(clazz.getClassName());
allClasses.put(name, clazz);
final ConstantPool pool = clazz.getConstantPool();
for (int i = 1; i < pool.getLength(); i++) {
final Constant cons = pool.getConstant(i);
// System.out.println("("+i+") " + cons );
if (cons != null && cons.getTag() == Const.CONSTANT_Class) {
final int idx = ((ConstantClass) pool.getConstant(i)).getNameIndex();
final String clas = ((ConstantUtf8) pool.getConstant(idx)).getBytes();
addClassString(clas, name);
}
}
}
/**
* the main of this class
*/
void go(final String[] args) throws IOException {
JavaClass clazz;
// sort the options
for (final 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(JavaClass.EXTENSION)) {
clName = clName.substring(0, clName.length() - JavaClass.EXTENSION.length());
}
clName = Utility.packageToPath(clName);
try (InputStream inputStream = classPath.getInputStream(clName)) {
clazz = new ClassParser(inputStream, 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 and add back to it
// and the allClasses list. see addDependents
while (!dependents.isEmpty()) {
final String name = dependents.firstKey();
final String from = dependents.remove(name);
if (allClasses.get(name) == null) {
try (InputStream inputStream = classPath.getInputStream(name)) {
clazz = new ClassParser(inputStream, name).parse();
addDependents(clazz);
} catch (final 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
try (JarOutputStream jarFile = new JarOutputStream(new FileOutputStream(defaultJar))) {
jarFile.setLevel(5); // use compression
int written = 0;
for (final Entry<String, JavaClass> entry : allClasses.entrySet()) { // add entries for every class
final JavaClass claz = allClasses.get(entry.getKey());
final ZipEntry zipEntry = new ZipEntry(entry.getValue() + JavaClass.EXTENSION);
final byte[] bytes = claz.getBytes();
final int length = bytes.length;
jarFile.putNextEntry(zipEntry);
jarFile.write(bytes, 0, length);
written += length; // for logging
}
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()) {
final 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() {
final List<String> names = new ArrayList<>(allClasses.keySet());
Collections.sort(names);
names.forEach(System.err::println);
}
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.");
}
}