| /* |
| * 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."); |
| } |
| } |