• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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