001: /*
002: * Copyright (c) 2001-2007, Jean Tessier
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * * Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in the
014: * documentation and/or other materials provided with the distribution.
015: *
016: * * Neither the name of Jean Tessier nor the names of his contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032:
033: package com.jeantessier.dependency;
034:
035: import java.util.*;
036:
037: import org.apache.log4j.*;
038:
039: import com.jeantessier.classreader.*;
040:
041: /**
042: * <p>Traverses a Classfile and extracts dependencies from its code.</p>
043: *
044: * <p>Known limitations:</p>
045: * <ul>
046: * <li>Does not see dependencies on <code>static final</code> constants of
047: * primitive types or String</li>
048: * <li>Does not look at local variables</li>
049: * </ul>
050: */
051: public class CodeDependencyCollector extends CollectorBase {
052: private NodeFactory factory;
053: private SelectionCriteria filterCriteria;
054:
055: private Node current;
056: private HashSet<DependencyListener> dependencyListeners = new HashSet<DependencyListener>();
057:
058: public CodeDependencyCollector() {
059: this (new NodeFactory());
060: }
061:
062: public CodeDependencyCollector(NodeFactory factory) {
063: this (factory, new ComprehensiveSelectionCriteria());
064: }
065:
066: public CodeDependencyCollector(NodeFactory factory,
067: SelectionCriteria filterCriteria) {
068: this .factory = factory;
069: this .filterCriteria = filterCriteria;
070: }
071:
072: public NodeFactory getFactory() {
073: return factory;
074: }
075:
076: public Collection<String> getCollection() {
077: return getFactory().getPackages().keySet();
078: }
079:
080: public void visitClassfile(Classfile classfile) {
081: current = getFactory().createClass(classfile.getClassName(),
082: true);
083:
084: fireBeginClass(classfile.toString());
085:
086: if (classfile.getSuperclassIndex() != 0) {
087: classfile.getRawSuperclass().accept(this );
088: }
089:
090: for (Class_info class_info : classfile.getAllInterfaces()) {
091: class_info.accept(this );
092: }
093:
094: for (Field_info field : classfile.getAllFields()) {
095: field.accept(this );
096: }
097:
098: for (Method_info method : classfile.getAllMethods()) {
099: method.accept(this );
100: }
101:
102: fireEndClass(classfile.toString());
103: }
104:
105: public void visitClass_info(Class_info entry) {
106: String classname = entry.getName();
107: if (Logger.getLogger(getClass()).isDebugEnabled()) {
108: Logger.getLogger(getClass()).debug("VisitClass_info():");
109: Logger.getLogger(getClass()).debug(
110: " name = \"" + classname + "\"");
111: }
112:
113: if (classname.startsWith("[")) {
114: processDescriptor(classname);
115: } else if (filterCriteria.isMatchingClasses()
116: && filterCriteria.matchesClassName(classname)) {
117: Node other = getFactory().createClass(classname);
118: current.addDependency(other);
119: if (Logger.getLogger(getClass()).isDebugEnabled()) {
120: Logger.getLogger(getClass()).info(
121: "Class_info dependency: " + current + " --> "
122: + other);
123: }
124: fireDependency(current, other);
125: }
126: }
127:
128: public void visitFieldRef_info(FieldRef_info entry) {
129: if (Logger.getLogger(getClass()).isDebugEnabled()) {
130: Logger.getLogger(getClass()).debug("VisitFieldRef_info():");
131: Logger.getLogger(getClass()).debug(
132: " class = \"" + entry.getClassName() + "\"");
133: Logger.getLogger(getClass()).debug(
134: " name = \""
135: + entry.getRawNameAndType().getName()
136: + "\"");
137: Logger.getLogger(getClass()).debug(
138: " type = \""
139: + entry.getRawNameAndType().getType()
140: + "\"");
141: }
142:
143: String signature = entry.getFullSignature();
144: if (filterCriteria.isMatchingFeatures()
145: && filterCriteria.matchesFeatureName(signature)) {
146: Node other = getFactory().createFeature(signature);
147: current.addDependency(other);
148: if (Logger.getLogger(getClass()).isDebugEnabled()) {
149: Logger.getLogger(getClass()).info(
150: "FieldRef_info dependency: " + current
151: + " --> " + other);
152: }
153: fireDependency(current, other);
154: }
155:
156: processDescriptor(entry.getRawNameAndType().getType());
157: }
158:
159: public void visitMethodRef_info(MethodRef_info entry) {
160: if (Logger.getLogger(getClass()).isDebugEnabled()) {
161: Logger.getLogger(getClass())
162: .debug("VisitMethodRef_info():");
163: Logger.getLogger(getClass()).debug(
164: " class = \"" + entry.getClassName() + "\"");
165: Logger.getLogger(getClass()).debug(
166: " name = \""
167: + entry.getRawNameAndType().getName()
168: + "\"");
169: Logger.getLogger(getClass()).debug(
170: " type = \""
171: + entry.getRawNameAndType().getType()
172: + "\"");
173: }
174:
175: if (!entry.isStaticInitializer()) {
176: String signature = entry.getFullSignature();
177: if (filterCriteria.isMatchingFeatures()
178: && filterCriteria.matchesFeatureName(signature)) {
179: Node other = getFactory().createFeature(signature);
180: current.addDependency(other);
181: if (Logger.getLogger(getClass()).isDebugEnabled()) {
182: Logger.getLogger(getClass()).info(
183: "MethodRef_info dependency: " + current
184: + " --> " + other);
185: }
186: fireDependency(current, other);
187: }
188:
189: processDescriptor(entry.getRawNameAndType().getType());
190: }
191: }
192:
193: public void visitInterfaceMethodRef_info(
194: InterfaceMethodRef_info entry) {
195: if (Logger.getLogger(getClass()).isDebugEnabled()) {
196: Logger.getLogger(getClass()).debug(
197: "VisitInterfaceMethodRef_info():");
198: Logger.getLogger(getClass()).debug(
199: " class = \"" + entry.getClassName() + "\"");
200: Logger.getLogger(getClass()).debug(
201: " name = \""
202: + entry.getRawNameAndType().getName()
203: + "\"");
204: Logger.getLogger(getClass()).debug(
205: " type = \""
206: + entry.getRawNameAndType().getType()
207: + "\"");
208: }
209:
210: String signature = entry.getFullSignature();
211: if (filterCriteria.isMatchingFeatures()
212: && filterCriteria.matchesFeatureName(signature)) {
213: Node other = getFactory().createFeature(signature);
214: current.addDependency(other);
215: if (Logger.getLogger(getClass()).isDebugEnabled()) {
216: Logger.getLogger(getClass()).info(
217: "InterfaceMethodRef_info dependency: "
218: + current + " --> " + other);
219: }
220: fireDependency(current, other);
221: }
222:
223: processDescriptor(entry.getRawNameAndType().getType());
224: }
225:
226: public void visitField_info(Field_info entry) {
227: if (Logger.getLogger(getClass()).isDebugEnabled()) {
228: Logger.getLogger(getClass()).debug("VisitField_info():");
229: Logger.getLogger(getClass()).debug(
230: " name = \"" + entry.getName() + "\"");
231: Logger.getLogger(getClass()).debug(
232: " descriptor = \"" + entry.getDescriptor()
233: + "\"");
234: }
235:
236: current = getFactory().createFeature(entry.getFullSignature(),
237: true);
238:
239: processDescriptor(entry.getDescriptor());
240:
241: super .visitField_info(entry);
242: }
243:
244: public void visitMethod_info(Method_info entry) {
245: if (Logger.getLogger(getClass()).isDebugEnabled()) {
246: Logger.getLogger(getClass()).debug("VisitMethod_info():");
247: Logger.getLogger(getClass()).debug(
248: " name = \"" + entry.getName() + "\"");
249: Logger.getLogger(getClass()).debug(
250: " descriptor = \"" + entry.getDescriptor()
251: + "\"");
252: }
253:
254: current = getFactory().createFeature(entry.getFullSignature(),
255: true);
256:
257: processDescriptor(entry.getDescriptor());
258:
259: super .visitMethod_info(entry);
260: }
261:
262: public void visitInstruction(Instruction helper) {
263: Logger.getLogger(getClass()).debug("VisitInstruction() ...");
264:
265: /*
266: * We can skip the "new" (0xbb) instruction as it is always
267: * followed by a call to the constructor method.
268: */
269:
270: switch (helper.getOpcode()) {
271: case 0x12: // ldc
272: case 0x13: // ldc_w
273: case 0xb2: // getstatic
274: case 0xb3: // putstatic
275: case 0xb4: // getfield
276: case 0xb5: // putfield
277: case 0xb6: // invokevirtual
278: case 0xb7: // invokespecial
279: case 0xb8: // invokestatic
280: case 0xb9: // invokeinterface
281: // case 0xbb: // new
282: case 0xbd: // anewarray
283: case 0xc0: // checkcast
284: case 0xc1: // instanceof
285: case 0xc5: // multianewarray
286: helper.getIndexedConstantPoolEntry().accept(this );
287: break;
288: default:
289: // Do nothing
290: break;
291: }
292:
293: super .visitInstruction(helper);
294: }
295:
296: public void visitExceptionHandler(ExceptionHandler helper) {
297: if (Logger.getLogger(getClass()).isDebugEnabled()) {
298: Logger.getLogger(getClass()).debug(
299: getClass().getName() + "VisitExceptionHandler(): "
300: + helper);
301: }
302:
303: if (helper.getCatchTypeIndex() != 0) {
304: helper.getRawCatchType().accept(this );
305: }
306: }
307:
308: private void processDescriptor(String str) {
309: int currentPos = 0;
310: int startPos;
311: int endPos;
312:
313: while ((startPos = str.indexOf('L', currentPos)) != -1) {
314: if ((endPos = str.indexOf(';', startPos)) != -1) {
315: String classname = SignatureHelper.path2ClassName(str
316: .substring(startPos + 1, endPos));
317: if (filterCriteria.isMatchingClasses()
318: && filterCriteria.matchesClassName(classname)) {
319: if (Logger.getLogger(getClass()).isDebugEnabled()) {
320: Logger.getLogger(getClass()).debug(
321: " Adding \"" + classname + "\"");
322: }
323: Node other = getFactory().createClass(classname);
324: current.addDependency(other);
325: if (Logger.getLogger(getClass()).isDebugEnabled()) {
326: Logger.getLogger(getClass()).info(
327: "descriptor dependency: " + current
328: + " --> " + other);
329: }
330: fireDependency(current, other);
331: }
332: currentPos = endPos + 1;
333: } else {
334: currentPos = startPos + 1;
335: }
336: }
337: }
338:
339: public void addDependencyListener(DependencyListener listener) {
340: synchronized (dependencyListeners) {
341: dependencyListeners.add(listener);
342: }
343: }
344:
345: public void removeDependencyListener(DependencyListener listener) {
346: synchronized (dependencyListeners) {
347: dependencyListeners.remove(listener);
348: }
349: }
350:
351: protected void fireBeginSession() {
352: DependencyEvent event = new DependencyEvent(this );
353:
354: HashSet<DependencyListener> listeners;
355: synchronized (dependencyListeners) {
356: listeners = (HashSet<DependencyListener>) dependencyListeners
357: .clone();
358: }
359:
360: for (DependencyListener listener : listeners) {
361: listener.beginSession(event);
362: }
363: }
364:
365: protected void fireBeginClass(String classname) {
366: DependencyEvent event = new DependencyEvent(this , classname);
367:
368: HashSet<DependencyListener> listeners;
369: synchronized (dependencyListeners) {
370: listeners = (HashSet<DependencyListener>) dependencyListeners
371: .clone();
372: }
373:
374: for (DependencyListener listener : listeners) {
375: listener.beginClass(event);
376: }
377: }
378:
379: protected void fireDependency(Node dependent, Node dependable) {
380: DependencyEvent event = new DependencyEvent(this , dependent,
381: dependable);
382:
383: HashSet<DependencyListener> listeners;
384: synchronized (dependencyListeners) {
385: listeners = (HashSet<DependencyListener>) dependencyListeners
386: .clone();
387: }
388:
389: for (DependencyListener listener : listeners) {
390: listener.dependency(event);
391: }
392: }
393:
394: protected void fireEndClass(String classname) {
395: DependencyEvent event = new DependencyEvent(this , classname);
396:
397: HashSet<DependencyListener> listeners;
398: synchronized (dependencyListeners) {
399: listeners = (HashSet<DependencyListener>) dependencyListeners
400: .clone();
401: }
402:
403: for (DependencyListener listener : listeners) {
404: listener.endClass(event);
405: }
406: }
407:
408: protected void fireEndSession() {
409: DependencyEvent event = new DependencyEvent(this );
410:
411: HashSet<DependencyListener> listeners;
412: synchronized (dependencyListeners) {
413: listeners = (HashSet<DependencyListener>) dependencyListeners
414: .clone();
415: }
416:
417: for (DependencyListener listener : listeners) {
418: listener.endSession(event);
419: }
420: }
421: }
|