001: /*
002: $Header: /cvsroot/xorm/xorm/tools/src/org/xorm/tools/jar/JarClassFinder.java,v 1.2 2003/04/25 01:28:51 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with Foobar; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.tools.jar;
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.InputStream;
026: import java.io.IOException;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.jar.JarEntry;
032: import java.util.jar.JarInputStream;
033:
034: /** Utility for finding classes of a particular type within a JAR file.
035: @author Dan Checkoway
036: */
037: public class JarClassFinder extends ClassLoader {
038: /** The default max number of iterations when loading classes */
039: public static final int DEFAULT_MAX_ITERATIONS = 50;
040:
041: /** The max number of iterations when loading classes */
042: protected int maxIterations = DEFAULT_MAX_ITERATIONS;
043: protected HashMap fileToClasses = new HashMap();
044:
045: /** Create a JarClassFinder using the default max number of iterations */
046: public JarClassFinder() {
047: }
048:
049: /** Create a JarClassFinder using a specific max number of iterations */
050: public JarClassFinder(int maxIterations) {
051: if (maxIterations <= 0) {
052: throw new IllegalArgumentException(
053: "Invalid maxIterations value: " + maxIterations);
054: }
055: this .maxIterations = maxIterations;
056: }
057:
058: /** Find classes of a certain type within a JAR file
059: @param baseClass the type of class to look for
060: @param jarFileName the name/path of the JAR file to examine
061: @return a Collection of matching Class objects, or an empty
062: Collection if no matching classes are found
063: */
064: public Collection findClassesOfType(Class baseClass,
065: String jarFileName) throws IOException {
066: return findClassesOfType(baseClass, new File(jarFileName));
067: }
068:
069: /** Find classes of a certain type within a JAR file
070: @param baseClass the type of class to look for
071: @param jarFile the JAR file to examine
072: @return a Collection of matching Class objects, or an empty
073: Collection if no matching classes are found
074: */
075: public Collection findClassesOfType(Class baseClass, File jarFile)
076: throws IOException {
077: // Check if this jarFile has been cached previously
078: Collection resolved = (Collection) fileToClasses.get(jarFile);
079: if (resolved == null) {
080: if (jarFile.isDirectory()) {
081: resolved = resolveImpl(getClassData(jarFile, ""));
082: } else {
083: resolved = resolveImpl(getClassData(new FileInputStream(
084: jarFile)));
085: }
086: fileToClasses.put(jarFile, resolved);
087: }
088: return findClassesOfType(baseClass, resolved);
089: }
090:
091: private Collection findClassesOfType(Class baseClass,
092: Collection resolved) {
093: Collection matchingClasses = new ArrayList();
094: Iterator it = resolved.iterator();
095: while (it.hasNext()) {
096: Class clazz = (Class) it.next();
097: if (!baseClass.getName().equals(clazz.getName())
098: && baseClass.isAssignableFrom(clazz)) {
099: // Yep, this is the type we're looking for
100: matchingClasses.add(clazz);
101: }
102: }
103: return matchingClasses;
104: }
105:
106: /** Find classes of a certain type within JAR content
107: @param baseClass the type of class to look for
108: @param inputStream an input stream with the JAR content to examine
109: @return a Collection of matching Class objects, or an empty
110: Collection if no matching classes are found
111: */
112: public Collection findClassesOfType(Class baseClass,
113: InputStream inputStream) throws IOException {
114: return findClassesOfType(baseClass,
115: resolveImpl(getClassData(inputStream)));
116: }
117:
118: private ArrayList getClassData(File directory, String prefix)
119: throws IOException {
120: // First we will iterate through all of the entries in the directory,
121: // finding all .class files, and creating a list of classes to resolve.
122: ArrayList unresolved = new ArrayList();
123: File[] files = directory.listFiles();
124: for (int i = 0; i < files.length; i++) {
125: String filename = files[i].getName();
126: if (filename.endsWith(".class")) {
127: String className = prefix
128: + filename.substring(0, filename.length() - 6);
129: byte[] classData = readClassData(new FileInputStream(
130: files[i]));
131: ClassData uc = new ClassData(className, classData);
132: unresolved.add(uc);
133: } else if (files[i].isDirectory()) {
134: String newPrefix = prefix + filename + ".";
135: unresolved.addAll(getClassData(files[i], newPrefix));
136: }
137: }
138: return unresolved;
139: }
140:
141: private ArrayList getClassData(InputStream inputStream)
142: throws IOException {
143: // First we will iterate through all of the entries in the JAR,
144: // finding all .class files, and creating a list of classes to resolve.
145: ArrayList unresolved = new ArrayList();
146: JarInputStream jarInput = new JarInputStream(inputStream);
147: JarEntry jarEntry;
148: while ((jarEntry = jarInput.getNextJarEntry()) != null) {
149: String fileName = jarEntry.getName();
150: if (!jarEntry.isDirectory() && fileName.endsWith(".class")) {
151: String className = fileName.substring(0,
152: fileName.length() - 6).replace('/', '.');
153: byte[] classData = readClassData(jarInput);
154: ClassData uc = new ClassData(className, classData);
155: unresolved.add(uc);
156: }
157: }
158: return unresolved;
159: }
160:
161: private Collection resolveImpl(ArrayList unresolved) {
162: // Now we will iterate through the list of unresolved classes, trying
163: // to load each class and seeing if the base class is assignable
164: // from it. If loading the class fails, we leave it on the unresolved
165: // list, since we will iterate through the unresolved list repeatedly
166: // (up to maxIterations) until all classes are loaded and checked.
167: ArrayList matchingClasses = new ArrayList();
168: for (int it = 0; !unresolved.isEmpty() && it < maxIterations; ++it) {
169: for (int k = 0; k < unresolved.size(); ++k) {
170: ClassData classDef = (ClassData) unresolved.get(k);
171: try {
172: // Attempt to load the class
173: Class clazz;
174: try {
175: // Mimic the way classes are loaded by the VM
176: // by checking in the system classpath first
177: clazz = findSystemClass(classDef.getClassName());
178: } catch (ClassNotFoundException e) {
179: clazz = defineClass(classDef.getClassName(),
180: classDef.getClassData(), 0, classDef
181: .getClassData().length);
182: }
183: // It loaded ok, so remove it from the unresolved list
184: unresolved.remove(k--);
185: matchingClasses.add(clazz);
186: } catch (NoClassDefFoundError e) {
187: // There's a dependency...so keep it on the unresolved
188: // list and we'll come back to it in the next iteration.
189: } catch (LinkageError e) {
190: // This happens when there's a "duplicate class definition"
191: // error. Just remove the class from the unresolved list.
192: unresolved.remove(k--);
193: } catch (SecurityException e) {
194: // This happens when there's a "Prohibited package name"
195: // error. Just remove the class from the unresolved list.
196: unresolved.remove(k--);
197: }
198: }
199: }
200:
201: if (!unresolved.isEmpty()) {
202: throw new RuntimeException(
203: "JarClassFinder was unable to load all classes from the JAR..."
204: + unresolved.size()
205: + " unresolved classes after "
206: + maxIterations + " iterations: "
207: + unresolved);
208: }
209:
210: return matchingClasses;
211: }
212:
213: /** Read class data for a JAR entry
214: @param input the JAR input stream
215: @return a byte array representing the class data
216: */
217: protected byte[] readClassData(InputStream input)
218: throws IOException {
219: ByteArrayOutputStream baos = new ByteArrayOutputStream();
220: byte[] buf = new byte[8192];
221: int nbytes;
222: while ((nbytes = input.read(buf, 0, buf.length)) >= 0) {
223: baos.write(buf, 0, nbytes);
224: }
225: return baos.toByteArray();
226: }
227:
228: /** Test stub */
229: public static void main(String[] args) {
230: int exitCode = 0;
231: try {
232: String baseClassName = args[0];
233: String jarFileName = args[1];
234:
235: Class baseClass = Class.forName(baseClassName);
236:
237: JarClassFinder jcf = new JarClassFinder();
238: Collection classes = jcf.findClassesOfType(baseClass,
239: jarFileName);
240: if (classes.isEmpty()) {
241: System.out.println("No matching classes found.");
242: } else {
243: System.out.println("Classes of type "
244: + baseClass.getName() + ":");
245: for (Iterator iter = classes.iterator(); iter.hasNext();) {
246: Class clazz = (Class) iter.next();
247: System.out.println(clazz.getName());
248: }
249: }
250: } catch (Throwable t) {
251: t.printStackTrace(System.out);
252: exitCode = 1;
253: } finally {
254: Runtime.getRuntime().exit(exitCode);
255: }
256: }
257:
258: private static class ClassData {
259: String className;
260: byte[] classData;
261:
262: public ClassData(String className, byte[] classData) {
263: this .className = className;
264: this .classData = classData;
265: }
266:
267: public String getClassName() {
268: return className;
269: }
270:
271: public byte[] getClassData() {
272: return classData;
273: }
274:
275: public String toString() {
276: return className; // + " (" + classData.length + " bytes)";
277: }
278:
279: public boolean equals(Object obj) {
280: if (!(obj instanceof ClassData)) {
281: return false;
282: }
283: ClassData other = (ClassData) obj;
284: if (other == this ) {
285: return true;
286: } else {
287: return getClassName().equals(other.getClassName());
288: }
289: }
290: }
291: }
|