001: package com.reeltwo.jumble.dependency;
002:
003: import com.reeltwo.util.CLIFlags.Flag;
004: import com.reeltwo.util.CLIFlags;
005: import java.util.ArrayList;
006: import java.util.Collection;
007: import java.util.HashMap;
008: import java.util.HashSet;
009: import java.util.Iterator;
010: import java.util.Set;
011: import java.util.Stack;
012: import org.apache.bcel.util.Repository;
013: import org.apache.bcel.classfile.Constant;
014: import org.apache.bcel.classfile.ConstantClass;
015: import org.apache.bcel.classfile.ConstantPool;
016: import org.apache.bcel.classfile.ConstantUtf8;
017: import org.apache.bcel.classfile.Field;
018: import org.apache.bcel.classfile.JavaClass;
019: import org.apache.bcel.classfile.Method;
020: import org.apache.bcel.generic.ArrayType;
021: import org.apache.bcel.generic.Type;
022: import org.apache.bcel.util.ClassPath;
023: import org.apache.bcel.util.SyntheticRepository;
024:
025: /**
026: * Class for extracting dependencies from class files
027: *
028: * @author Tin Pavlinic
029: * @version $Revision: 496 $
030: */
031: public class DependencyExtractor {
032:
033: /** CONSTANT_Class value. */
034: private static final byte CONSTANT_CLASS = 7;
035:
036: /** CONSTANT_Utf8 value. */
037: private static final byte CONSTANT_UTF8 = 1;
038:
039: private final ClassPath mClassPath;
040:
041: private final Repository mRepository;
042:
043: /** The name of the class being analyzed. */
044: private String mClassName;
045:
046: /** Set of packages to ignore. (Subpackages are ignored automatically) */
047: private Set mIgnoredPackages;
048:
049: /** A cache for the classes which have already been analyzed */
050: private HashMap mCache;
051:
052: /**
053: * Main method. Displays the dependencies for the class given as a
054: * command-line parameter.
055: *
056: * @param args
057: * the command line arguments. Only accepts one argument: the name of
058: * the class to analyze for dependencies.
059: */
060: public static void main(String[] args) {
061: CLIFlags flags = new CLIFlags("DependencyExtractor");
062: Flag ignoreFlag = flags.registerOptional('i', "ignore",
063: String.class, "METHOD",
064: "Comma-separated list of packages to exclude.");
065: Flag classpathFlag = flags.registerOptional('c', "classpath",
066: String.class, "CLASSPATH",
067: "The classpath to use for tests", System
068: .getProperty("java.class.path"));
069: Flag classFlag = flags.registerRequired(String.class, "CLASS",
070: "Name of the class to analyse.");
071: flags.setFlags(args);
072:
073: Set ignore = null;
074: if (ignoreFlag.isSet()) {
075: ignore = new HashSet();
076: String[] tokens = ((String) ignoreFlag.getValue())
077: .split(",");
078: for (int i = 0; i < tokens.length; i++) {
079: ignore.add(tokens[i]);
080: }
081: }
082: final String className = ((String) classFlag.getValue())
083: .replace('/', '.');
084:
085: System.out.println("Dependencies for " + className);
086: System.out.println();
087:
088: DependencyExtractor extractor = new DependencyExtractor(
089: (String) classpathFlag.getValue());
090:
091: if (ignore != null) {
092: extractor.setIgnoredPackages(ignore);
093: }
094:
095: Collection dependencies = extractor.getAllDependencies(
096: className, true);
097: Iterator it = dependencies.iterator();
098: while (it.hasNext()) {
099: System.out.println(it.next());
100: }
101: }
102:
103: /**
104: * Constructor
105: */
106: public DependencyExtractor(String classPath) {
107: mCache = new HashMap();
108: mIgnoredPackages = new HashSet();
109: mClassPath = new ClassPath(classPath);
110: mRepository = SyntheticRepository.getInstance(mClassPath);
111:
112: mIgnoredPackages.add("java");
113: mIgnoredPackages.add("javax");
114: mIgnoredPackages.add("sun");
115: mIgnoredPackages.add("com.sun");
116: mIgnoredPackages.add("org.w3c");
117: mIgnoredPackages.add("org.xml");
118: mIgnoredPackages.add("org.omg");
119: mIgnoredPackages.add("org.ietf");
120: //integrity();
121: }
122:
123: /**
124: * Gets the name of the root class
125: *
126: * @return Returns the class name
127: */
128: public String getClassName() {
129: return mClassName;
130: }
131:
132: /**
133: * Sets the source class name
134: *
135: * @param className
136: * The new class name to set.
137: */
138: public void setClassName(String className) {
139: mClassName = className;
140: //integrity();
141: }
142:
143: /**
144: * Finds classes that the given class depends on. Runs non recursively
145: *
146: * @param className
147: * the name of the class to check
148: * @return the names of the dependency classes in a Collection
149: */
150: private Collection getDependencies(String className, boolean ignore) {
151: //First look in the cache
152: if (mCache.containsKey(className)) {
153: return (Collection) mCache.get(className);
154: }
155:
156: ArrayList ret = new ArrayList();
157: if (isPrimitiveArray(className)) {
158: // System.out.println(className + " primitive array");
159: return ret;
160: }
161: className = cleanClassName(className);
162:
163: JavaClass clazz = loadClass(className);
164:
165: if (clazz == null) {
166: System.err.println("Could not find " + className);
167: return ret;
168: }
169: ConstantPool cp = clazz.getConstantPool();
170:
171: for (int i = 0; i < cp.getLength(); i++) {
172: Constant c = cp.getConstant(i);
173: if (c == null) {
174: continue;
175: }
176: if (c.getTag() == CONSTANT_CLASS) {
177: ConstantClass cc = (ConstantClass) c;
178: ConstantUtf8 utf8 = (ConstantUtf8) cp.getConstant(cc
179: .getNameIndex(), CONSTANT_UTF8);
180: ret.add(utf8.getBytes().replaceAll("/", "."));
181: }
182: }
183:
184: Set methodTypes = new HashSet();
185: Method[] methods = clazz.getMethods();
186: for (int i = 0; i < methods.length; i++) {
187: methodTypes.add(methods[i].getReturnType());
188: for (int j = 0; j < methods[i].getArgumentTypes().length; j++) {
189: methodTypes.add(methods[i].getArgumentTypes()[j]);
190: }
191: }
192:
193: Set stringTypes = getStringTypes(methodTypes);
194: ret.addAll(stringTypes);
195:
196: Set fieldTypes = new HashSet();
197: Field[] fields = clazz.getFields();
198:
199: for (int i = 0; i < fields.length; i++) {
200: fieldTypes.add(fields[i].getType());
201: }
202: ret.addAll(getStringTypes(fieldTypes));
203:
204: if (ignore) {
205: ret = new ArrayList(filterSystemClasses(ret));
206: }
207: mCache.put(className, ret);
208: return ret;
209: }
210:
211: private JavaClass loadClass(String className) {
212: try {
213: JavaClass clazz = mRepository.findClass(className);
214:
215: if (clazz == null) {
216: return mRepository.loadClass(className);
217: } else {
218: return clazz;
219: }
220: } catch (ClassNotFoundException ex) {
221: return null;
222: }
223: }
224:
225: /**
226: * Gets the immediate dependencies of the class
227: *
228: * @param ignore
229: * a flag indicating whether to ignore system classes
230: * @return a Collection of class names of the dependencies.
231: */
232: public Collection getImmediateDependencies(boolean ignore) {
233: Collection ret;
234: if (!isPrimitiveArray(getClassName())) {
235: if (ignore) {
236: ret = filterSystemClasses(getDependencies(
237: cleanClassName(getClassName()), true));
238: } else {
239: ret = getDependencies(cleanClassName(getClassName()),
240: false);
241: }
242: } else {
243: // System.out.println("gid - " + getClassName() + " is primitive");
244: ret = new HashSet();
245: }
246: //integrity();
247: return ret;
248: }
249:
250: /**
251: * Gets all of the dependencies of the class
252: *
253: * @param ignore
254: * a flag indicating whether to ignore system classes
255: * @return a Collection of class names of the dependencies.
256: */
257: public Collection getAllDependencies(String rootClass,
258: boolean ignore) {
259: setClassName(rootClass);
260: Stack fringe = new Stack();
261: HashSet ret = new HashSet();
262:
263: if (isPrimitiveArray(getClassName())) {
264: // System.out.println(getClassName() + " primitive array");
265: return ret;
266: }
267:
268: fringe.addAll(getImmediateDependencies(ignore));
269:
270: while (!fringe.isEmpty()) {
271: String cur = (String) fringe.pop();
272: // System.out.println(cur);
273: if (!isPrimitiveArray(cur)) {
274: cur = cleanClassName(cur);
275: // System.out.println(cur);
276: if (!ret.contains(cur)) {
277: ret.add(cur);
278: fringe.addAll(getDependencies(cur, ignore));
279: }
280: }
281: }
282:
283: if (ret.contains(getClassName())) {
284: ret.remove(getClassName());
285: }
286: if (ignore) {
287: ret = new HashSet(filterSystemClasses(ret));
288: }
289:
290: //integrity();
291:
292: return ret;
293: }
294:
295: /**
296: * Filters the ignored classes from the collection and creates a new
297: * collection.
298: * @param c the collection to filter
299: * @return the filtered collection
300: */
301: private Collection filterSystemClasses(Collection c) {
302: ArrayList ret = new ArrayList();
303: Iterator it = c.iterator();
304:
305: while (it.hasNext()) {
306: String cur = (String) it.next();
307: Iterator packages = getIgnoredPackages().iterator();
308: boolean allowed = true;
309: while (packages.hasNext()) {
310: String pack = (String) packages.next();
311: if (cur.startsWith(pack + ".")) {
312: allowed = false;
313: }
314: }
315: if (allowed) {
316: ret.add(cur);
317: }
318: }
319: return ret;
320: }
321:
322: /**
323: * Gets a Set of packages (as strings) ignored by the dependency extractor.
324: * All subpackages are ignored also
325: *
326: * @return the ignored packages.
327: */
328: public Set getIgnoredPackages() {
329: return mIgnoredPackages;
330: }
331:
332: /**
333: * Sets the set of packages to ignore. All subpackages are ignored alse
334: *
335: * @param newIgnore
336: * mew ignore set.
337: */
338: public void setIgnoredPackages(Set newIgnore) {
339: mIgnoredPackages = newIgnore;
340: }
341:
342: public static String cleanClassName(String className) {
343: while (className.startsWith("[")) {
344: className = className.substring(1);
345: }
346:
347: if (className.endsWith(";") && className.startsWith("L")) {
348: className = className.substring(1);
349: className = className.substring(0, className.length() - 1);
350: }
351:
352: return className;
353: }
354:
355: /**
356: * Determines whether the string <CODE>s</CODE> is the internal
357: * representation of a primitive type.
358: *
359: * @param s
360: * the string to check
361: * @return true is <CODE>s</CODE> represents a primitive type
362: */
363: public static boolean isPrimitiveArray(String s) {
364: if (s.startsWith("[")) {
365: s = cleanClassName(s);
366: return s.equals("B") || s.equals("C") || s.equals("D")
367: || s.equals("F") || s.equals("I") || s.equals("J")
368: || s.equals("S") || s.equals("Z")
369: || s.startsWith("L");
370: }
371: return false;
372: }
373:
374: // public void integrity() {
375: // // Class must exist
376: // try {
377: // Class.forName(mClassName);
378: // } catch (ClassNotFoundException e) {
379: // assert false;
380: // }
381: // }
382:
383: public Set getStringTypes(Collection c) {
384: Set s = new HashSet();
385:
386: Iterator it = c.iterator();
387:
388: while (it.hasNext()) {
389: Type t = (Type) it.next();
390:
391: while (t instanceof ArrayType) {
392: t = ((ArrayType) t).getBasicType();
393: }
394:
395: if (t == Type.BOOLEAN) {
396: continue;
397: } else if (t == Type.BYTE) {
398: continue;
399: } else if (t == Type.CHAR) {
400: continue;
401: } else if (t == Type.DOUBLE) {
402: continue;
403: } else if (t == Type.FLOAT) {
404: continue;
405: } else if (t == Type.INT) {
406: continue;
407: } else if (t == Type.LONG) {
408: continue;
409: } else if (t == Type.SHORT) {
410: continue;
411: } else if (t == Type.VOID) {
412: continue;
413: } else {
414: s.add(t.toString());
415: }
416: }
417: return s;
418: }
419:
420: public void clearCache() {
421: mCache.clear();
422: }
423: }
|