1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 import java.io.FileOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.TreeMap; 27 import java.util.jar.JarOutputStream; 28 import java.util.zip.ZipEntry; 29 30 import org.apache.bcel.Constants; 31 import org.apache.bcel.classfile.ClassParser; 32 import org.apache.bcel.classfile.Constant; 33 import org.apache.bcel.classfile.ConstantClass; 34 import org.apache.bcel.classfile.ConstantPool; 35 import org.apache.bcel.classfile.ConstantUtf8; 36 import org.apache.bcel.classfile.JavaClass; 37 import org.apache.bcel.util.ClassPath; 38 39 /** 40 * Package the client. Creates a jar file in the current directory 41 * that contains a minimal set of classes needed to run the client. 42 * 43 * Use BCEL to extract class names and read/write classes 44 * 45 */ 46 public class Package { 47 48 /** 49 * The name of the resulting jar is Client.jar 50 */ 51 static String defaultJar = "Client.jar"; 52 53 /* 54 * See usage() for arguments. Create an instance and run that 55 *(just so not all members have to be static) 56 */ main(String args[])57 static void main(String args[]) { 58 Package instance = new Package(); 59 try { 60 instance.go(args); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 instance.usage(); 64 } 65 } 66 67 /** 68 * We use a "default ClassPath object which uses the environments 69 * CLASSPATH 70 */ 71 ClassPath classPath = ClassPath.SYSTEM_CLASS_PATH; 72 73 /** 74 * A map for all Classes, the ones we're going to package. 75 * Store class name against the JavaClass. From the JavaClass 76 * we get the bytes to create the jar. 77 */ 78 Map<String, JavaClass> allClasses = new TreeMap<String, JavaClass>(); 79 80 /** 81 * We start at the root classes, put them in here, then go through 82 * this list, putting dependent classes in here and from there 83 * into allClasses. Store class names against class names of their dependents 84 */ 85 TreeMap<String, String> dependents = new TreeMap<String, String>(); 86 87 /** 88 * Collect all classes that could not be found in the classpath. 89 * Store class names against class names of their dependents 90 */ 91 TreeMap<String, String> notFound = new TreeMap<String, String>(); 92 93 /** 94 * See wheather we print the classes that were not found (default = false) 95 */ 96 boolean showNotFound = false; 97 /** 98 * Remember wheather to print allClasses at the end (default = false) 99 */ 100 boolean printClasses = false; 101 /** 102 * Wheather we log classes during processing (default = false) 103 */ 104 boolean log = false; 105 usage()106 public void usage() { 107 System.out.println(" This program packages classes and all their dependents"); 108 System.out.println(" into one jar. Give all starting classes (your main)"); 109 System.out.println(" on the command line. Use / as separator, the .class is"); 110 System.out.println(" optional. We use the environments CLASSPATH to resolve"); 111 System.out.println(" classes. Anything but java.* packages are packaged."); 112 System.out.println(" If you use Class.forName (or similar), be sure to"); 113 System.out.println(" include the classes that you load dynamically on the"); 114 System.out.println(" command line.\n"); 115 System.out.println(" These options are recognized:"); 116 System.out.println(" -e -error Show errors, meaning classes that could not "); 117 System.out.println(" resolved + the classes that referenced them."); 118 System.out.println(" -l -log Show classes as they are processed. This will"); 119 System.out.println(" include doubles, java classes and is difficult to"); 120 System.out.println(" read. I use it as a sort of progress monitor"); 121 System.out.println(" -s -show Prints all the classes that were packaged"); 122 System.out.println(" in alphabetical order, which is ordered by package"); 123 System.out.println(" for the most part."); 124 } 125 126 /** 127 * the main of this class 128 */ go(String[] args)129 void go(String[] args) throws IOException { 130 JavaClass clazz; 131 // sort the options 132 for (String arg : args) { 133 if (arg.startsWith("-e")) { 134 showNotFound = true; 135 continue; 136 } 137 if (arg.startsWith("-s")) { 138 printClasses = true; 139 continue; 140 } 141 if (arg.startsWith("-l")) { 142 log = true; 143 continue; 144 } 145 String clName = arg; 146 if (clName.endsWith(".class")) { 147 clName = clName.substring(0, clName.length() - 6); 148 } 149 clName = clName.replace('.', '/'); 150 clazz = new ClassParser(classPath.getInputStream(clName), clName).parse(); 151 // here we create the root set of classes to process 152 addDependents(clazz); 153 System.out.println("Packaging for class: " + clName); 154 } 155 156 if (dependents.isEmpty()) { 157 usage(); 158 return; 159 } 160 161 System.out.println("Creating jar file: " + defaultJar); 162 163 // starting processing: Grab from the dependents list an add back to it 164 // and the allClasses list. see addDependents 165 while (!dependents.isEmpty()) { 166 String name = dependents.firstKey(); 167 String from = dependents.remove(name); 168 if (allClasses.get(name) == null) { 169 try { 170 InputStream is = classPath.getInputStream(name); 171 clazz = new ClassParser(is, name).parse(); 172 addDependents(clazz); 173 } catch (IOException e) { 174 //System.err.println("Error, class not found " + name ); 175 notFound.put(name, from); 176 } 177 } 178 } 179 180 if (printClasses) { // if wanted show all classes 181 printAllClasses(); 182 } 183 184 // create the jar 185 JarOutputStream jarFile = new JarOutputStream(new FileOutputStream(defaultJar)); 186 jarFile.setLevel(5); // use compression 187 int written = 0; 188 for (String name : allClasses.keySet()) { // add entries for every class 189 JavaClass claz = allClasses.get(name); 190 ZipEntry zipEntry = new ZipEntry(name + ".class"); 191 byte[] bytes = claz.getBytes(); 192 int length = bytes.length; 193 jarFile.putNextEntry(zipEntry); 194 jarFile.write(bytes, 0, length); 195 written += length; // for logging 196 } 197 jarFile.close(); 198 System.err.println("The jar file contains " + allClasses.size() 199 + " classes and contains " + written + " bytes"); 200 201 if (!notFound.isEmpty()) { 202 System.err.println(notFound.size() + " classes could not be found"); 203 if (showNotFound) { // if wanted show the actual classes that we not found 204 while (!notFound.isEmpty()) { 205 String name = notFound.firstKey(); 206 System.err.println(name + " (" + notFound.remove(name) + ")"); 207 } 208 } else { 209 System.err.println("Use '-e' option to view classes that were not found"); 210 } 211 } 212 } 213 214 /** 215 * Print all classes that were packaged. Sort alphabetically for better 216 * overview. Enabled by -s option 217 */ printAllClasses()218 void printAllClasses() { 219 List<String> names = new ArrayList<String>(allClasses.keySet()); 220 Collections.sort(names); 221 for (int i = 0; i < names.size(); i++) { 222 String cl = names.get(i); 223 System.err.println(cl); 224 } 225 } 226 227 /** 228 * Add this class to allClasses. Then go through all its dependents 229 * and add them to the dependents list if they are not in allClasses 230 */ addDependents(JavaClass clazz)231 void addDependents(JavaClass clazz) throws IOException { 232 String name = clazz.getClassName().replace('.', '/'); 233 allClasses.put(name, clazz); 234 ConstantPool pool = clazz.getConstantPool(); 235 for (int i = 1; i < pool.getLength(); i++) { 236 Constant cons = pool.getConstant(i); 237 //System.out.println("("+i+") " + cons ); 238 if (cons != null && cons.getTag() == Constants.CONSTANT_Class) { 239 int idx = ((ConstantClass) pool.getConstant(i)).getNameIndex(); 240 String clas = ((ConstantUtf8) pool.getConstant(idx)).getBytes(); 241 addClassString(clas, name); 242 } 243 } 244 } 245 246 /** 247 * add given class to dependents (from is where its dependent from) 248 * some fiddeling to be done because of array class notation 249 */ addClassString(String clas, String from)250 void addClassString(String clas, String from) throws IOException { 251 if (log) { 252 System.out.println("processing: " + clas + " referenced by " + from); 253 } 254 255 // must check if it's an arrary (start with "[") 256 if (clas.startsWith("[")) { 257 if (clas.length() == 2) { 258 // it's an array of built in type, ignore 259 return; 260 } 261 if ('L' == clas.charAt(1)) { 262 // it's an array of objects, the class name is between [L and ; 263 // like [Ljava/lang/Object; 264 addClassString(clas.substring(2, clas.length() - 1), from); 265 return; 266 } 267 if ('[' == clas.charAt(1)) { 268 // it's an array of arrays, call recursive 269 addClassString(clas.substring(1), from); 270 return; 271 } 272 throw new IOException("Can't recognize class name =" + clas); 273 } 274 275 if (!clas.startsWith("java/") && allClasses.get(clas) == null) { 276 dependents.put(clas, from); 277 // System.out.println(" yes" ); 278 } else { 279 // System.out.println(" no" ); 280 } 281 } 282 } 283