Source code: com/port80/eclipse/jdt/graph/DependGraphAction.java
1 package com.port80.eclipse.jdt.graph;
2
3 import java.awt.Color;
4 import java.io.File;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.PrintWriter;
8 import java.lang.reflect.InvocationTargetException;
9 import java.util.HashMap;
10 import java.util.HashSet;
11 import java.util.Iterator;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.TreeSet;
15
16 import org.eclipse.core.runtime.IProgressMonitor;
17 import org.eclipse.core.runtime.Path;
18 import org.eclipse.core.runtime.SubProgressMonitor;
19 import org.eclipse.jdt.core.ISourceRange;
20 import org.eclipse.jdt.core.dom.CompilationUnit;
21 import org.eclipse.jdt.core.dom.IMethodBinding;
22 import org.eclipse.jdt.core.dom.ITypeBinding;
23 import org.eclipse.jdt.core.dom.Modifier;
24 import org.eclipse.jdt.internal.corext.SourceRange;
25 import org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart;
26 import org.eclipse.jface.action.IAction;
27 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
28 import org.eclipse.jface.operation.IRunnableWithProgress;
29 import org.eclipse.jface.viewers.ISelection;
30 import org.eclipse.jface.viewers.ISelectionProvider;
31 import org.eclipse.jface.viewers.IStructuredSelection;
32 import org.eclipse.swt.SWT;
33 import org.eclipse.swt.widgets.FileDialog;
34 import org.eclipse.swt.widgets.Shell;
35 import org.eclipse.ui.IWorkbenchWindow;
36 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
37 import org.eclipse.ui.PartInitException;
38
39 import com.port80.eclipse.jdt.JdtPlugin;
40 import com.port80.eclipse.jdt.util.JavaUtil;
41 import com.port80.eclipse.util.GraphEditorInput;
42 import com.port80.graph.GraphException;
43 import com.port80.graph.IEdge;
44 import com.port80.graph.IGraph;
45 import com.port80.graph.IVertex;
46 import com.port80.graph.dot.impl.Dot;
47 import com.port80.graph.impl.DirectedGraph;
48 import com.port80.util.Msg;
49 import com.port80.util.attr.ColorFactory;
50 import com.port80.util.attr.IAttrTable;
51
52 /**
53 * Extract an method call graph in UML like format for a given set of classes (<b>'selected'</b> objects).
54 *
55 * Each vertex represent either a caller or a callee.
56 * Caller is a method or a class labelled with method that calls external methods.
57 * Callee is method that is called by a caller.
58 * Edges of type '-hasCalls' represent calls from the caller vertex to the callee vertex.
59 * Edges of type '-typehier' represent type herierhical from superclass to derived classes.
60 * Edges of type '-implemented' represent that the head vertex (method) implements tail vertex (interface).
61 *
62 * @see IWorkbenchWindowActionDelegate
63 */
64 public class DependGraphAction implements IWorkbenchWindowActionDelegate {
65
66 ////////////////////////////////////////////////////////////////////////
67
68 private static final String NAME = "DependGraphAction";
69 private static final boolean DEBUG = false;
70 private static final boolean VERBOSE = true;
71
72 // Vertex color
73 private static final Color COLOR_TOP = ColorFactory.create("0xffe040"); // "ff9090";
74 private static final Color COLOR_UNCONNECTED = ColorFactory.create("0xc0c0c0");
75 private static final Color COLOR_NESTED_CLASS = ColorFactory.create("0xffe020");
76 private static final Color COLOR_CLASS = ColorFactory.create("0xffff20");
77 private static final Color COLOR_INTERFACE = ColorFactory.create("0xffffe0");
78 // "0xb0e0ff"; // "0xffd0a0"; // "0xa8d8ff"; // azure1"; // "0xfff0e8";
79 private static final Color COLOR_METHOD = ColorFactory.create("0xb0f040"); // "greenyellow";
80 private static final Color COLOR_CONSTRUCTOR = ColorFactory.create("0xc8e8c8"); // "greenyellow";
81 private static final Color COLOR_CALLEE_CLASS = ColorFactory.create("0xe8e890");
82 private static final Color COLOR_CALLEE_INTERFACE = ColorFactory.create("0xf0f0d0");
83 private static final Color COLOR_CALLEE_METHOD = ColorFactory.create("0xd8f8d8");
84 // Edge color
85 private static final Color COLOR_SUPERCLASS = ColorFactory.create("0xff0000");
86 private static final Color COLOR_SUPERINTERFACE = ColorFactory.create("0xff9090");
87 private static final Color COLOR_OVERRIDEN = ColorFactory.create("0xff0000");
88 private static final Color COLOR_IMPLEMENTED = ColorFactory.create("0xff9090");
89 //
90 private static final String STYLE_UNCONNECTED = "dotted";
91 private static final String STYLE_SUPERCLASS = "solid,3.0";
92 private static final String STYLE_SUPERINTERFACE = "solid,3.0";
93 private static final String STYLE_OVERRIDEN = "dotted,3.0";
94 private static final String STYLE_IMPLEMENTED = "dotted,3.0";
95 //
96 private static final String STYLE = "solid";
97 private static final String STYLE_CALLEE = "none";
98 private static final String STYLE_TOP = "solid,3.0";
99 //private static final String ARROW_SUPERCLASS = "dot,8";
100 //private static final String ARROW_SUPERINTERFACE = "odot,8";
101 //
102 private static final int MAX_LABELWIDTH = 40;
103 private static final String COLOR_CALLEE = "0xc02020";
104 private static final String COLOR_CALLEE_PARAMS = "0x606060";
105 private static final String COLOR_PUBLIC = "0xe00000";
106 private static final String COLOR_PROTECTED = "0x2020ff"; // "0x0000c0";
107 private static final String COLOR_PACKAGE = "seagreen"; // "0x006000";
108 private static final String COLOR_PRIVATE = "0x000000";
109
110 static class Options {
111
112 public static final int DEPEND_METHOD = 0;
113 public static final int DEPEND_REFERENCE = 1;
114 public static final int SCOPE_SELECTED = 0;
115 public static final int SCOPE_CONNECTED = 1;
116 public static final int DETAIL_CLASS = 0;
117 public static final int DETAIL_METHOD = 1;
118 public static final int DETAIL_CLASSWITHMETHOD = 2;
119
120 public static final String[] MSG_DEPENDS = new String[] { "Method Call", "Type Reference" };
121 public static final String[] MSG_SCOPES = new String[] { "Selected", "Selected + connected" };
122 public static final String[] MSG_DETAILS =
123 new String[] { "Classes", "Methods", "Classes with called methods" };
124
125 int depend = DEPEND_METHOD;
126 int scope = SCOPE_SELECTED;
127 int detail = DETAIL_CLASS;
128 boolean show = true;
129 String seed;
130 String passes;
131 public int getSeed() {
132 try {
133 return Integer.parseInt(seed);
134 } catch (Exception e) {
135 return -7;
136 }
137 }
138 public int getPasses() {
139 try {
140 return Integer.parseInt(passes);
141 } catch (Exception e) {
142 return -7;
143 }
144 }
145 }
146
147 ////////////////////////////////////////////////////////////////////////
148
149 IGraph fGraph;
150 Object[] fSelected;
151 Options fOptions;
152 ISourceRange fParsingErrorRange;
153 String fOutDir;
154
155 ////////////////////////////////////////////////////////////////////////
156
157 public DependGraphAction() {
158 fOptions = new Options();
159 }
160
161 /**
162 * The action has been activated. The argument of the
163 * method represents the 'real' action sitting
164 * in the workbench UI.
165 * @see IWorkbenchWindowActionDelegate#run
166 */
167 public void run(IAction action) {
168 PackageExplorerPart view = JavaUtil.getPackageView();
169 if (view == null)
170 return;
171 ISelectionProvider provider = (ISelectionProvider) view.getSite().getSelectionProvider();
172 fSelected = ((IStructuredSelection) provider.getSelection()).toArray();
173 if (fSelected.length == 0)
174 return;
175 Shell shell=JdtPlugin.getActiveWorkbenchShell();
176 if (!DependGraphOptionsDialog
177 .getOptions(fOptions, fSelected, shell))
178 return;
179 //
180 try {
181 new ProgressMonitorDialog(shell).run(true, true, new DependGraphRunnable());
182 } catch (InvocationTargetException e) {
183 Msg.err(e);
184 } catch (InterruptedException e) {
185 Msg.warn(NAME + ",run(IAction): Cancelled");
186 }
187 if (fGraph == null)
188 return;
189 if (VERBOSE)
190 System.err.println(fGraph.sprintGraph());
191 if (DEBUG)
192 System.err.println(fGraph.sprintGraph());
193 saveGraph(fGraph);
194 }
195
196 private void saveGraph(IGraph graph) {
197 FileDialog dialog = new FileDialog(JdtPlugin.getActiveWorkbenchShell(), SWT.SAVE);
198 if (fOutDir == null)
199 fOutDir = JdtPlugin.getWorkspace().getRoot().getLocation().toString();
200 dialog.setFilterPath(fOutDir);
201 String fname = dialog.open();
202 if (fname != null && fname.length() > 0) {
203 System.err.println(NAME + ".saveGraph(): filename=" + fname);
204 fOutDir = new Path(fname).removeLastSegments(1).toString();
205 if (!fname.endsWith(".dot"))
206 fname += ".dot";
207 File ifile = Msg.createFile(fname);
208 if (ifile != null) {
209 try {
210 PrintWriter writer = new PrintWriter(new FileOutputStream(ifile));
211 writer.println(fGraph.sprintGraph());
212 writer.close();
213 } catch (FileNotFoundException e) {
214 System.err.println(
215 NAME + ".saveGraph(): Can't open output .dot file: " + fname);
216 return;
217 }
218 }
219 if (fOptions.show) {
220 Dot.dot(fGraph, fOptions.getSeed(), fOptions.getPasses());
221 try {
222 JdtPlugin.getActiveWorkbenchWindow().getActivePage().openEditor(
223 new GraphEditorInput(fname, fGraph),
224 "com.port80.eclipse.jdt.graph.views.GraphViewer");
225 } catch (PartInitException e) {
226 Msg.err(e);
227 }
228 }
229 }
230 }
231
232 private class DependGraphRunnable implements IRunnableWithProgress {
233 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
234 if (fOptions.depend == Options.DEPEND_REFERENCE) {
235 fGraph = referenceGraph(fSelected, monitor);
236 } else {
237 switch (fOptions.detail) {
238 case Options.DETAIL_METHOD :
239 fGraph = methodCallGraph(fSelected, monitor);
240 break;
241 case Options.DETAIL_CLASS :
242 case Options.DETAIL_CLASSWITHMETHOD :
243 default :
244 fGraph = classCallGraph(fSelected, monitor);
245 break;
246 }
247 }
248 if (monitor.isCanceled()) {
249 if (VERBOSE)
250 Msg.warn(NAME + ".DependGraphRunnable(): Canceled");
251 fGraph = null;
252 }
253 }
254 }
255
256 /**
257 * Selection in the workbench has been changed. We
258 * can change the state of the 'real' action here
259 * if we want, but this can only happen after
260 * the delegate has been created.
261 * @see IWorkbenchWindowActionDelegate#selectionChanged
262 */
263 public void selectionChanged(IAction action, ISelection selection) {
264 }
265
266 /**
267 * We can use this method to dispose of any system
268 * resources we previously allocated.
269 * @see IWorkbenchWindowActionDelegate#dispose
270 */
271 public void dispose() {
272 }
273
274 /**
275 * We will cache window object in order to
276 * be able to provide parent shell for the message dialog.
277 * @see IWorkbenchWindowActionDelegate#init
278 */
279 public void init(IWorkbenchWindow window) {
280 }
281
282 ////////////////////////////////////////////////////////////////////////
283
284 /**
285 * Construct class dependency graph between classes in given objects base on type references.
286 *
287 * @return The graph with type as vertices and reference relationship as edges.
288 * @param selected The JavaElement operate on.
289 */
290 IGraph referenceGraph(Object[] selected, IProgressMonitor monitor) {
291 final String PREFIX = NAME + ".dependGraph(): ";
292 // Fully qualified typename->filename mapping for source/binary types among the selected objects.
293 Map filemap = new HashMap();
294 IGraph graph = new DirectedGraph("TypeReferenceGraph", null, null);
295 //graph.setAttr("ranksep", 1.1);
296 //graph.setAttr("nodesep", 1.1);
297 graph.setAttr("criticaluseinbus", true);
298 graph.setAttr("ptp_percent", 100);
299 IAttrTable vtable = graph.getVertexAttrTable();
300 vtable.setAttr("fontsize", 11.0);
301 vtable.setAttr("inthreshold", 6);
302 vtable.setAttr("outthreshold", 6);
303 //
304 // Create vertices.
305 //
306 monitor.beginTask("Reference Graph", 100);
307 int style = SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK;
308 GraphUtil.resolveReferences(
309 selected,
310 graph,
311 filemap,
312 new ReferenceGraphBuilder(),
313 new SubProgressMonitor(monitor, 90, style));
314 //
315 // Create edges.
316 //
317 Set vset = graph.getVertexSet();
318 SubProgressMonitor submon = new SubProgressMonitor(monitor, 10, style);
319 submon.beginTask("Creating graph", vset.size());
320 String typename;
321 for (Iterator uit = vset.iterator(); uit.hasNext();) {
322 submon.worked(1);
323 IVertex src = (IVertex) uit.next();
324 connectInheritance(src, graph);
325 if (fOptions.detail == Options.DETAIL_CLASS)
326 continue;
327 Set references = (Set) src.getAttr("-references");
328 if (references == null)
329 continue;
330 IEdge e;
331 IVertex dest;
332 for (Iterator it = references.iterator(); it.hasNext();) {
333 typename = (String) it.next();
334 dest = graph.getVertex(typename);
335 if (dest == null)
336 continue;
337 if (dest.equals(src))
338 continue;
339 if (src.findEdgesTo(dest, "-hasReference", null) != null)
340 continue;
341 try {
342 e = graph.newEdge(src, dest, null, null);
343 e.setAttr("-hasReference", true);
344 } catch (GraphException ex) {
345 Msg.err(PREFIX + "new edge: src=" + src + ", dest=" + dest, ex);
346 }
347 }
348 }
349 //
350 Set vertices = graph.allVertices();
351 for (Iterator it = vertices.iterator(); it.hasNext();) {
352 IVertex v = (IVertex) it.next();
353 removeInheritedDepends(v, graph, filemap);
354 }
355 for (Iterator it = vertices.iterator(); it.hasNext();) {
356 IVertex v = (IVertex) it.next();
357 if (DEBUG)
358 System.err.println(PREFIX + "v=" + v + ", inSize=" + v.inSize());
359 //if(v.inSize()==0) v.setAttrFromString("style","filled,3.0");
360 if (v.inSize() == 0 && v.outSize() != 0) {
361 //v.setAttrFromString("fillcolor", COLOR_TOP); //style","solid,3.0");
362 v.setAttr("label", "<b>" + v.getAttr("label") + "</b>");
363 }
364 if (!filemap.containsKey(v.getName())) {
365 setCalleeStyle(v);
366 }
367 v.removeUnregisteredAttrs();
368 }
369 submon.done();
370 monitor.done();
371 return graph;
372 }
373
374 /**
375 * Construct class dependency graph between classes in given objects base on method calls.
376 *
377 * @return The graph with type as vertices and reference relationship as edges.
378 * @param selected The JavaElement operate on.
379 */
380 IGraph classCallGraph(Object[] selected, IProgressMonitor monitor) {
381 final String PREFIX = NAME + ".classGraph(): ";
382 // Fully qualified typename->filename mapping for source/binary types among the selected objects.
383 Map filemap = new HashMap();
384 IGraph graph = new DirectedGraph("ClassCallGraph", null, null);
385 //graph.setAttr("ranksep", 1.1);
386 //graph.setAttr("nodesep", 1.1);
387 graph.setAttr("criticaluseinbus", true);
388 graph.setAttr("ptp_percent", 100);
389 IAttrTable vtable = graph.getVertexAttrTable();
390 vtable.setAttr("fontsize", 11.0);
391 vtable.setAttr("style", STYLE);
392 vtable.setAttr("inthreshold", 6);
393 vtable.setAttr("outthreshold", 6);
394 //
395 // Create vertices.
396 //
397 monitor.beginTask("Class CallGraph", 100);
398 int style = SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK;
399 GraphUtil.resolveReferences(
400 selected,
401 graph,
402 filemap,
403 new ClassCallGraphBuilder(),
404 new SubProgressMonitor(monitor, 90, style));
405 //
406 // Create edges.
407 //
408 Set vset = graph.allVertices();
409 IProgressMonitor submon = new SubProgressMonitor(monitor, 10);
410 submon.beginTask("Creating graph", vset.size());
411 for (Iterator uit = vset.iterator(); uit.hasNext();) {
412 submon.worked(1);
413 String typename;
414 IEdge e;
415 IVertex src = (IVertex) uit.next();
416 connectInheritance(src, graph);
417 Set invokes = (Set) src.getAttr("-invokes");
418 if (invokes == null)
419 continue;
420 for (Iterator it = invokes.iterator(); it.hasNext();) {
421 MethodWrapper method = (MethodWrapper) it.next();
422 typename = method.getFullDeclaringTypeName();
423 IVertex dest = graph.getVertex(typename);
424 if (dest == null) {
425 if (fOptions.scope == Options.SCOPE_CONNECTED) {
426 Msg.err(
427 NAME
428 + ".classGraph(): dest vertex not exists: typename="
429 + typename);
430 }
431 continue;
432 }
433 if (fOptions.detail == Options.DETAIL_CLASSWITHMETHOD) {
434 Set callees = (Set) dest.getAttr("-callees");
435 if (callees == null) {
436 callees = new TreeSet();
437 dest.setAttr("-callees", callees);
438 }
439 callees.add(method);
440 }
441 if (dest.equals(src))
442 continue;
443 if (src.findEdgesTo(dest, "-hasCalls", null) != null)
444 continue;
445 try {
446 e = graph.newEdge(src, dest, null, null);
447 e.setAttr("-hasCalls", true);
448 } catch (GraphException ex) {
449 Msg.err(PREFIX + "new edge: src=" + src + ", dest=" + dest, ex);
450 }
451 }
452 }
453 for (Iterator uit = vset.iterator(); uit.hasNext();) {
454 IVertex v = (IVertex) uit.next();
455 StringBuffer label = new StringBuffer(v.getAttrString("label"));
456 //if(v.inSize()==0) v.setAttr("style","filled,3.0");
457 if (v.inSize() == 0) {
458 //v.setAttr("fillcolor", COLOR_TOP); //style","solid,3.0");
459 label.insert(0, "<b>");
460 label.append("</b>");
461 }
462 // Signatures of methods get called by others.
463 Set callees = (Set) v.getAttr("-callees");
464 if (fOptions.detail == Options.DETAIL_CLASS || callees == null) {
465 v.setAttr("label", label.toString());
466 } else {
467 addCallees(callees, label, v);
468 }
469 }
470 //
471 for (Iterator it = vset.iterator(); it.hasNext();) {
472 IVertex v = (IVertex) it.next();
473 if (!filemap.containsKey(v.getName())) {
474 setCalleeStyle(v);
475 }
476 v.removeUnregisteredAttrs();
477 }
478 submon.done();
479 monitor.done();
480 return graph;
481 }
482
483 IGraph methodCallGraph(Object[] selected, IProgressMonitor monitor) {
484 final String PREFIX = NAME + ".methodGraph(): ";
485 // Fully qualified typename->filename mapping for source/binary types among the selected objects.
486 Map filemap = new HashMap();
487 IGraph graph = new DirectedGraph("MethodCallGraph", null, null);
488 //graph.setAttr("ranksep", 1.1);
489 //graph.setAttr("nodesep", 1.1);
490 graph.setAttr("criticaluseinbus", true);
491 graph.setAttr("ptp_percent", 100);
492 IAttrTable vtable = graph.getVertexAttrTable();
493 vtable.setAttr("fontsize", 11.0);
494 vtable.setAttr("style", STYLE);
495 vtable.setAttr("inthreshold", 6);
496 vtable.setAttr("outthreshold", 6);
497 //
498 // Create vertices.
499 //
500 monitor.beginTask("Method CallGraph", 100);
501 int style = SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK;
502 GraphUtil.resolveReferences(
503 selected,
504 graph,
505 filemap,
506 new MethodCallGraphBuilder(),
507 new SubProgressMonitor(monitor, 90, style));
508 //
509 // Create edges.
510 //
511 IVertex src, dest;
512 IEdge e;
513 Set vset = graph.getVertexSet();
514 IProgressMonitor submon = new SubProgressMonitor(monitor, 10);
515 submon.beginTask("Creating graph", vset.size());
516 for (Iterator uit = vset.iterator(); uit.hasNext();) {
517 submon.worked(1);
518 String typename;
519 src = (IVertex) uit.next();
520 if (src.getAttrBool("-isType"))
521 connectInheritance(src, graph);
522 Set implemented = (Set) src.getAttr("-implemented");
523 if (implemented != null)
524 connectImplemented(implemented, src, graph);
525 Set invokes = (Set) src.getAttr("-invokes");
526 if (invokes == null)
527 continue;
528 for (Iterator it = invokes.iterator(); it.hasNext();) {
529 MethodWrapper method = (MethodWrapper) it.next();
530 dest = graph.getVertex(method.getMethodSignature());
531 if (dest == null) {
532 // Try if a type vertex present.
533 typename = method.getFullDeclaringTypeName();
534 if (filemap.get(typename) != null) {
535 // Selected methods should use type vertex only if method is a constructor.
536 if (!method.isConstructor()) {
537 Msg.err(
538 PREFIX
539 + "selected method vertex not exists: typename="
540 + typename
541 + ", method="
542 + method.getFullName());
543 continue;
544 }
545 }
546 dest = graph.getVertex(typename);
547 if (dest == null)
548 continue;
549 }
550 if (dest.equals(src))
551 continue;
552 if (src.findEdgesTo(dest, "-hasCalls", null) != null)
553 continue;
554 try {
555 e = graph.newEdge(src, dest, null, null);
556 e.setAttr("-hasCalls", true);
557 } catch (GraphException ex) {
558 Msg.err(PREFIX + "new edge: src=" + src + ", dest=" + dest, ex);
559 }
560 }
561 }
562 for (Iterator uit = vset.iterator(); uit.hasNext();) {
563 IVertex v = (IVertex) uit.next();
564 StringBuffer label = new StringBuffer(v.getAttrString("label"));
565 if (v.inSize() == 0) {
566 if (v.outSize() == 0) {
567 v.setAttr("style", STYLE_UNCONNECTED);
568 } else {
569 v.setAttr("style", STYLE_TOP);
570 }
571 }
572 // Signatures of methods get called by others.
573 Set callees = (Set) v.getAttr("-callees");
574 if (callees == null) {
575 v.setAttr("label", label.toString());
576 } else {
577 addCallees(callees, label, v);
578 }
579 //
580 if (!filemap.containsKey(v.getName())) {
581 // Referenced type.
582 setCalleeStyle(v);
583 }
584 v.removeUnregisteredAttrs();
585 }
586 submon.done();
587 monitor.done();
588 return graph;
589 }
590
591 ////////////////////////////////////////////////////////////////////////
592
593 /**
594 * Find superclass and super interfaces for the given type and added them to the given vertex.
595 * v.getAttr("-superClasses")=Set of full typename of all super classes.
596 * v.getAttr("-superInterfaces")=Set of full typename of all super interfaces.
597 *
598 * @return Set of all superclasses and superinterface ITypeBinding.
599 */
600 Set findSupers(TypeWrapper type, IVertex v) {
601 ITypeBinding parent;
602 String typename;
603 Set ret = new HashSet();
604 parent = type.getBinding().getSuperclass();
605 if (parent != null) {
606 Set dest = (Set) v.getAttr("-superClasses");
607 if (dest == null) {
608 dest = new HashSet();
609 v.setAttr("-superClasses", dest);
610 }
611 typename = GraphUtil.getFullTypeName(parent);
612 dest.add(typename);
613 ret.add(parent);
614 }
615 ITypeBinding[] a = type.getBinding().getInterfaces();
616 if (a != null && a.length > 0) {
617 Set dest = (Set) v.getAttr("-superInterfaces");
618 if (dest == null) {
619 dest = new HashSet();
620 v.setAttr("-superInterfaces", dest);
621 }
622 for (int i = 0; i < a.length; ++i) {
623 typename = GraphUtil.getFullTypeName(a[i]);
624 dest.add(typename);
625 ret.add(a[i]);
626 }
627 }
628 return ret;
629 }
630
631 /**
632 * Find all interfaces that the method implement.
633 *
634 * @enter Vertices for the superclass and superinterfaces of the type
635 * that declared the given method should have been created.
636 *
637 * @param method The method.
638 * @param vertex The vertex that represent the method.
639 * @param graph The top level graph.
640 */
641 void findImplemented(MethodWrapper method, IVertex vertex, IGraph graph) {
642 IMethodBinding binding = method.getBinding();
643 String signature = GraphUtil.getSimpleMethodName(binding) + GraphUtil.getMethodParamSignature(binding);
644 ITypeBinding parent = method.getDeclaringTypeBinding();
645 Set implemented = (Set) vertex.getAttr("-implemented");
646 if (implemented == null) {
647 implemented = new HashSet();
648 vertex.setAttr("-implemented", implemented);
649 }
650 String typename;
651 IVertex v;
652 Set methods;
653 ITypeBinding type = parent.getSuperclass();
654 if ((type != null) && (typename = filterType(GraphUtil.getFullTypeName(type))) != null) {
655 // Overriden superclasses
656 v = graph.getVertex(typename);
657 if (v == null) {
658 if (fOptions.scope == Options.SCOPE_CONNECTED)
659 Msg.err(
660 NAME
661 + ".findImplemented(): superclass vertex not found: typename="
662 + typename);
663 } else {
664 // Cache the method names for repeating search on the same type later.
665 methods = (Set) v.getAttr("-methods");
666 if (methods == null) {
667 methods = getDeclaredMethodSignatures(type, null);
668 v.setAttr("-methods", methods);
669 }
670 if (methods.contains(signature))
671 implemented.add(v);
672 }
673 }
674 // Implemented interfaces
675 ITypeBinding[] interfaces = parent.getInterfaces();
676 for (int i = 0; i < interfaces.length; ++i) {
677 type = interfaces[i];
678 typename = GraphUtil.getFullTypeName(type);
679 // Find vertex that represent the interface.
680 v = graph.getVertex(typename);
681 if (v == null) {
682 if (fOptions.scope == Options.SCOPE_CONNECTED)
683 Msg.err(
684 NAME
685 + ".findImplemented(): interface vertex not found: typename="
686 + typename);
687 continue;
688 }
689 methods = (Set) v.getAttr("-methods");
690 if (methods == null) {
691 methods = getDeclaredMethodSignatures(type, null);
692 v.setAttr("-methods", methods);
693 }
694 if (methods.contains(signature))
695 implemented.add(v);
696 }
697 if (DEBUG) {
698 System.err.println(NAME + ".findImplemented(): signature=" + signature);
699 for (Iterator it = implemented.iterator(); it.hasNext();) {
700 System.err.println("\n\t" + it.next());
701 }
702 }
703 }
704
705 /**
706 * Create vertices for each superclass and superinterface.
707 */
708 void createSuperVertices(Set types, IGraph graph) {
709 TypeWrapper type;
710 for (Iterator it = types.iterator(); it.hasNext();) {
711 type = new TypeWrapper((ITypeBinding) it.next());
712 if (type.getFullName().equals("java.lang.Object"))
713 continue;
714 createVertex(type, graph);
715 }
716 }
717
718 /**
719 * Reference to method of selected classes.
720 * . Any such methods should be added when processing the declared methods
721 * so should be ignored where referenced except for methods that are synthetic.
722 * Synthetic methods are not declared and so we need to create the method vertex
723 * for them when they are being referenced.
724 *
725 * @enter Vertex for the 'declaring' type of the method should have been created.
726 * @param filemap FullTypeName/FullMethodSignature->IVertex mapping of selected objects.
727 */
728 void addSyntheticMethod(MethodWrapper method, IGraph graph, Map filemap) {
729 String methodname = method.getMethodSignature();
730 if (graph.getVertex(methodname) != null)
731 // Method vertex already exists.
732 return;
733 String typename = method.getFullDeclaringTypeName();
734 IMethodBinding binding = method.getBinding();
735 if (!binding.isSynthetic() && !binding.isConstructor()) {
736 // NOTE: isSynthetic() returned false for synthesised default constructor!
737 Msg.err(
738 NAME
739 + ".addSyntheticMethod(): method is not synthetic/constructor: method="
740 + methodname);
741 }
742 if (VERBOSE)
743 System.err.println(NAME + ".addSyntheticMethod(): added: " + methodname);
744 IVertex v = graph.getVertex(typename);
745 if (v == null) {
746 Msg.err(
747 NAME
748 + ".addSyntheticMethod(): declaring type vertex not exist: typename="
749 + typename
750 + "\n\tmethod="
751 + method.getFullName());
752 return;
753 }
754 Set callees = (Set) v.getAttr("-callees");
755 if (callees == null) {
756 callees = new TreeSet();
757 v.setAttr("-callees", callees);
758 }
759 callees.add(method);
760 }
761
762 /**
763 * Create a vertex for the caller (type/method) if not exists, otherwise return existing vertex.
764 * @return IVertex created or existing vertex.
765 */
766 IVertex createVertex(Object wrapper, IGraph graph) {
767 String name = null;
768 IVertex ret = null;
769 try {
770 if (wrapper instanceof TypeWrapper) {
771 TypeWrapper type = (TypeWrapper) wrapper;
772 name = type.getFullName();
773 ret = graph.getVertex(name);
774 if (ret == null) {
775 ret = graph.newVertex(name, null);
776 ret.setAttr("label", type.getSimpleName());
777 ret.setAttr("-isType", true);
778 ret.setAttr("-modifiers", new Integer(type.getModifiers()));
779 if (type.isInterface()) {
780 ret.setAttr("-isInterface", true);
781 ret.setAttr("fillcolor", COLOR_INTERFACE);
782 } else if (name.indexOf('$') > 0 || name.indexOf('{') > 0)
783 ret.setAttr("fillcolor", COLOR_NESTED_CLASS);
784 else
785 ret.setAttr("fillcolor", COLOR_CLASS);
786 }
787 } else if (wrapper instanceof MethodWrapper) {
788 MethodWrapper method = (MethodWrapper) wrapper;
789 IMethodBinding binding = method.getBinding();
790 name = method.getMethodSignature();
791 ret = graph.getVertex(name);
792 if (ret == null) {
793 ret = graph.newVertex(name, null);
794 name = GraphUtil.chop(method.getSimpleName(), MAX_LABELWIDTH);
795 String typename =
796 GraphUtil.chop(method.getSimpleDeclaringTypeName(), MAX_LABELWIDTH);
797 String params = wrapParam(method.getSimpleParamSignature(), MAX_LABELWIDTH);
798 int modifiers = method.getModifiers();
799 if (Modifier.isStatic(modifiers))
800 name = "<b>" + name + "</b>";
801 if (Modifier.isPublic(modifiers))
802 name = "<color \"" + COLOR_PUBLIC + "\">" + name + "</color>";
803 else if (Modifier.isProtected(modifiers))
804 name = "<color \"" + COLOR_PROTECTED + "\">" + name + "</color>";
805 else if (method.isPrivate())
806 name = "<color \"" + COLOR_PRIVATE + "\">" + name + "</color>";
807 else
808 name = "<color \"" + COLOR_PACKAGE + "\">" + name + "</color>";
809 boolean hasparams = !params.equals("()");
810 params = "<color \"" + COLOR_CALLEE_PARAMS + "\">" + params + "</color>";
811 ret.setAttr(
812 "label",
813 "<color \""
814 + COLOR_CALLEE_PARAMS
815 + "\">"
816 + typename
817 + "</color><hr>\n"
818 + name
819 + "</color>"
820 + (hasparams ? ("\n" + params) : params));
821 ret.setAttr("-modifiers", new Integer(modifiers));
822 if (binding.isConstructor())
823 ret.setAttr("fillcolor", COLOR_CONSTRUCTOR);
824 else
825 ret.setAttr("fillcolor", COLOR_METHOD);
826 }
827 }
828 } catch (GraphException e) {
829 Msg.err(NAME + ".newMethodVertex(): Error creating vertex: " + name);
830 return null;
831 }
832 return ret;
833 }
834
835 /**
836 * Filter out references to predefined packages.
837 * @param typename Full type name of type to be filtered.
838 */
839 String filterType(String typename) {
840 if (typename.startsWith("java.lang.") || typename.startsWith("java.util."))
841 return null;
842 return typename;
843 }
844
845 ////////////////////////////////////////////////////////////////////////
846
847 /**
848 * Find all vertices that can be reached by the super classes of the given vertices.
849 *
850 * @param ret Return the set of vertices reachable through the super classes of the given vertex.
851 */
852 private void findSuperConnected(IVertex vertex, IGraph graph, Set ret) {
853 Set supers = (Set) vertex.getAttr("-superClasses");
854 if (supers == null)
855 return;
856 IVertex v;
857 for (Iterator it = supers.iterator(); it.hasNext();) {
858 String typename = (String) it.next();
859 v = graph.getVertex(typename);
860 if (v == null)
861 continue;
862 if (v.equals(vertex)) {
863 Msg.err(NAME + ".findSuperConnected(): v=" + v.getName());
864 continue;
865 }
866 IEdge[] outs = v.outs();
867 for (int i = 0; i < outs.length; ++i) {
868 ret.add(outs[i].getHead());
869 }
870 findSuperConnected(v, graph, ret);
871 }
872 }
873
874 /**
875 * @return The Set of all method signatures of methods that declared in the given type.
876 */
877 private Set getDeclaredMethodSignatures(ITypeBinding type, Set ret) {
878 if (ret == null)
879 ret = new HashSet();
880 IMethodBinding[] methods = type.getDeclaredMethods();
881 for (int i = 0; i < methods.length; ++i) {
882 ret.add(
883 GraphUtil.getSimpleMethodName(methods[i])
884 + GraphUtil.getMethodParamSignature(methods[i]));
885 }
886 ITypeBinding parent = type.getSuperclass();
887 if (parent != null) {
888 getDeclaredMethodSignatures(parent, ret);
889 }
890 ITypeBinding[] a = type.getInterfaces();
891 if (a == null) {
892 if (DEBUG) {
893 System.err.println(
894 NAME + ".getMethodSignatures(): type=" + GraphUtil.getFullTypeName(type));
895 for (Iterator it = ret.iterator(); it.hasNext();) {
896 System.err.println("\t" + it.next());
897 }
898 }
899 } else {
900 for (int i = 0; i < a.length; ++i) {
901 getDeclaredMethodSignatures(a[i], ret);
902 }
903 }
904 return ret;
905 }
906
907 private void setCalleeStyle(IVertex v) {
908 v.setAttr("style", STYLE_CALLEE);
909 if (v.getAttrBool("-isType")) {
910 if (v.getAttrBool("-isInterface"))
911 v.setAttr("fillcolor", COLOR_CALLEE_INTERFACE);
912 else
913 v.setAttr("fillcolor", COLOR_CALLEE_CLASS);
914 } else
915 v.setAttr("fillcolor", COLOR_CALLEE_METHOD);
916 }
917
918 /**
919 * Breakup param string to fit in given line width.
920 * @todo Unit test.
921 */
922 private String wrapParam(String params, int max) {
923 if (params.length() <= max)
924 return params;
925 StringBuffer buf = new StringBuffer();
926 int space = max;
927 int index;
928 while (params.length() > 0) {
929 index = params.indexOf(',');
930 if (index < 0)
931 index = params.length();
932 else
933 ++index;
934 if (index < space) {
935 buf.append(params.substring(0, index));
936 params = params.substring(index);
937 space -= index;
938 } else {
939 if (space != max) {
940 buf.append("\n");
941 space = max;
942 }
943 if (index > max) {
944 buf.append(params.substring(0, max - 3) + "...");
945 space = 0;
946 } else {
947 buf.append(params.substring(0, index));
948 space -= index;
949 }
950 params = params.substring(index);
951 }
952 }
953 return buf.toString();
954 }
955
956 /**
957 * Remove self edge and dependency that are already present in super classes.
958 * Color the edges that involve inheritance.
959 */
960 private void removeInheritedDepends(IVertex vertex, IGraph graph, Map filemap) {
961 Set ret = new HashSet();
962 findSuperConnected(vertex, graph, ret);
963 IVertex v;
964 IEdge e;
965 int n = 0;
966 IEdge[] outs = vertex.outs();
967 for (int i = 0; i < outs.length; ++i) {
968 e = outs[i];
969 if (!e.getAttrBool("-hasReference"))
970 continue;
971 v = e.getHead();
972 if (v.equals(vertex) || ret.contains(v)) {
973 v.removeInsFrom(vertex);
974 n += vertex.removeOutsTo(v);
975 if (DEBUG)
976 System.err.println("\tremoved= " + v.getName());
977 }
978 }
979 }
980
981 /**
982 * Create edge to superclass and superinterfaces.
983 **/
984 private void connectInheritance(IVertex src, IGraph graph) {
985 String typename;
986 IEdge e;
987 IVertex v;
988 //
989 Set supers = (Set) src.getAttr("-superClasses");
990 if (supers == null)
991 supers = new HashSet();
992 Set superif = (Set) src.getAttr("-superInterfaces");
993 if (superif != null)
994 supers.addAll(superif);
995 for (Iterator it = supers.iterator(); it.hasNext();) {
996 typename = (String) it.next();
997 IVertex dest = graph.getVertex(typename);
998 if (dest == null)
999 continue;
1000 if (src.findEdgesTo(dest, "-typehier", null) != null) {
1001 continue;
1002 }
1003 try {
1004 e = graph.newEdge(src, dest, null, null);
1005 e.setAttr("-typehier", true);
1006 e.setAttr("critical", true);
1007 v = e.getHead();
1008 if (v.getAttrBool("-isInterface")) {
1009 e.setAttr("style", STYLE_SUPERINTERFACE);
1010 e.setAttr("color", COLOR_SUPERINTERFACE);
1011 } else {
1012 e.setAttr("style", STYLE_SUPERCLASS);
1013 e.setAttr("color", COLOR_SUPERCLASS);
1014 }
1015 } catch (GraphException ex) {
1016 Msg.err(NAME + ".buildInheritance(): new edge: src=" + src + ", dest=" + dest, ex);
1017 }
1018 }
1019 }
1020
1021 /**
1022 * Connect method to interfaces that it implements.
1023 */
1024 private void connectImplemented(Set implemented, IVertex src, IGraph graph) {
1025 IVertex dest;
1026 IEdge e;
1027 for (Iterator it = implemented.iterator(); it.hasNext();) {
1028 dest = (IVertex) it.next();
1029 if (dest.equals(src))
1030 continue;
1031 if (src.findEdgesFrom(dest, "-implemented", null) != null)
1032 continue;
1033 try {
1034 e = graph.newEdge(dest, src, null, null);
1035 e.setAttr("-implemented", true);
1036 e.setAttr("critical", true);
1037 if (e.getHead().getAttrBool("-isInterface")) {
1038 e.setAttr("style", STYLE_IMPLEMENTED);
1039 e.setAttr("color", COLOR_IMPLEMENTED);
1040 } else {
1041 e.setAttr("style", STYLE_OVERRIDEN);
1042 e.setAttr("color", COLOR_OVERRIDEN);
1043 }
1044 } catch (GraphException ex) {
1045 Msg.err(NAME + ".connectImplemented(): new edge: src=" + src + ", dest=" + dest, ex);
1046 }
1047 }
1048 }
1049
1050 private void addCallees(Set callees, StringBuffer label, IVertex v) {
1051 String name, params;
1052 boolean first = true;
1053 for (Iterator it = callees.iterator(); it.hasNext();) {
1054 MethodWrapper method = (MethodWrapper) it.next();
1055 if (first) {
1056 label.append("<hr>");
1057 first = false;
1058 }
1059 name = method.getSimpleName();
1060 int len = name.length();
1061 if (len > MAX_LABELWIDTH - 4) {
1062 name = GraphUtil.chop(name, MAX_LABELWIDTH - 4);
1063 params = "(...";
1064 } else {
1065 params = GraphUtil.chop(method.getSimpleParamSignature(), MAX_LABELWIDTH - len);
1066 }
1067 label.append(
1068 "\n<left><color \""
1069 + COLOR_CALLEE
1070 + "\">"
1071 + name
1072 + "</color><color \""
1073 + COLOR_CALLEE_PARAMS
1074 + "\"> "
1075 + params
1076 + "</color>");
1077 }
1078 v.setAttr("label", label.toString());
1079 }
1080
1081 ////////////////////////////////////////////////////////////////////////
1082
1083 class ReferenceGraphBuilder implements IGraphAction {
1084 private final String NAME = "ReferenceGraphBuilder";
1085
1086 /**
1087 * Find type references in a compiled AST.
1088 *
1089 * @return Number of references found. A vertex is created in the graph for the srcpath and
1090 * vertex attributes -references, -superClasses, -superInterfaces holds the reference information.
1091 * @param srcpath The full path of the source unit, full type name (without .java) for binary class file.
1092 * @param ast The compiled ast for the source unit.
1093 * @param graph The return graph updated with the references.
1094 * @param filemap Return superclass map (srcpath->typename).
1095 */
1096 public int addRef(String srcpath, CompilationUnit ast, IGraph graph, Map filemap) {
1097 int n = 0;
1098 MethodReferenceASTVisitor visitor = null;
1099 try {
1100 visitor = MethodReferenceASTVisitor.startAt(ast);
1101 } catch (ASTError e) {
1102 fParsingErrorRange =
1103 new SourceRange(e.errorNode.getStartPosition(), e.errorNode.getLength());
1104 if (DEBUG)
1105 System.err.println(NAME + ".addRef(): parse error: " + e.errorNode.toString());
1106 }
1107 if (visitor == null) {
1108 Msg.err(NAME + ".addRef(): parse error: visitor==null: srcpath=" + srcpath);
1109 return 0;
1110 }
1111
1112 MethodReferenceASTVisitor.Result ret = visitor.getResult();
1113 //
1114 IVertex v;
1115 Set references;
1116 Map map;
1117 TypeWrapper wrapper;
1118 Set declares = ret.getDeclares();
1119 for (Iterator it = declares.iterator(); it.hasNext();) {
1120 wrapper = (TypeWrapper) it.next();
1121 v = createVertex(wrapper, graph);
1122 v.setAttr("alt1", srcpath);
1123 filemap.put(v.getName(), v);
1124 Set supers = findSupers(wrapper, v);
1125 if (fOptions.scope == Options.SCOPE_CONNECTED)
1126 createSuperVertices(supers, graph);
1127 }
1128 //
1129 Set destset;