1 /*
2 * Copyright (c) 2003-2008, by Henrik Arro and Contributors
3 *
4 * This file is part of JSeq, a tool to automatically create
5 * sequence diagrams by tracing program execution.
6 *
7 * See <http://jseq.sourceforge.net> for more information.
8 *
9 * JSeq is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of
12 * the License, or (at your option) any later version.
13 *
14 * JSeq is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with JSeq. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 package th.co.edge.jseq;
24
25 import com.sun.jdi.Method;
26
27 import th.co.edge.jseq.ActivationList.Filter;
28
29 /**
30 * A <code>Filter</code> that accepts all methods, except constructors that
31 * are either synthetic (automatically created by the compiler), or are calls to
32 * <code>super</code>.
33 *
34 * <p>
35 * The reason that this filter was created was to remove "unnecessary" calls to
36 * <code><init></code> from the sequence diagrams. Since the sequence
37 * diagrams generated by JSeq are class-based, not instance-based (i.e., each
38 * lifeline belongs to a class, not to a specific instance), and since the class
39 * of the instance being called is used (not the declaring class), calls to
40 * synthetic constructors or to the super constructor would be represented as
41 * one or several calls to <code><init></code> back to the same class.
42 * This was deemed more confusing than helpful.
43 *
44 * <p>
45 * In order to determine if a call is a call to a super-class constructor, this
46 * filter needs access to a <code>ClassLoader</code>. When running JSeq
47 * stand-alone, this is not a problem, but when attaching to a running process,
48 * JSeq should have the same classpath as that process. If not, the only effect
49 * is that the "unnecessary" constructor calls are included in the diagram, so
50 * failure to set up the correct classpath when attaching to a process is far
51 * from catastrophic.
52 *
53 * <p>
54 * This filter was created after Jacek Ratzinger supplied his patch (<a
55 * href="https://sourceforge.net/tracker/index.php?func=detail&aid=2027500&group_id=230519&atid=1080393"
56 * target="new">SourceForge issue 2027500</a>) since that change made calls to
57 * the super-class constructor look as calls back to the same class. In the
58 * process it also turned out that private nested classes use a synthetic
59 * constructor that need not be shown in the generated sequence diagrams.
60 */
61 public class ConstructorFilter implements Filter {
62 private ClassLoader classLoader;
63
64 /**
65 * Creates a new <code>ConstructorFilter</code> that uses the given
66 * <code>ClassLoader</code> to determine if a call is to a super-class
67 * constructor.
68 *
69 * @param classLoader
70 * a <code>ClassLoader</code> that should match that of the
71 * process being traced
72 */
73 public ConstructorFilter(ClassLoader classLoader) {
74 this.classLoader = classLoader;
75 }
76
77 /**
78 * Returns <code>true</code> for all <code>Activation</code>s, except
79 * calls to synthetic constructors and calls to super-class constructors.
80 *
81 * @param activation
82 * the <code>Activation</code> to check
83 *
84 * @return <code>false</code> if <code>activation</code> represents a
85 * call to a synthetic constructor or a call to a super-class
86 * constructor, <code>true</code> otherwise
87 */
88 @Override
89 public boolean accept(Activation activation) {
90 boolean accepted = true;
91 Method method = activation.getMethod();
92 if (method.isConstructor() && activation.getParent() != null) {
93 Activation parentActivation = activation.getParent();
94 Method parentMethod = parentActivation.getMethod();
95 if (parentMethod != null && parentMethod.isConstructor()) {
96 if (parentMethod.isSynthetic()) {
97 accepted = false;
98 } else if (isDeclaredInSuperclass(method, parentMethod)) {
99 accepted = false;
100 }
101 }
102 }
103 return accepted;
104 }
105
106 /**
107 * Returns <code>true</code> if the method <code>m1</code> is declared
108 * in a superclass to the class that declares the method <code>m2</code>.
109 *
110 * @param m1
111 * method from superclass?
112 * @param m2
113 * method from subclass?
114 *
115 * @return <code>true</code> if <code>m1</code> is declared in a
116 * superclass to the class where <code>m2</code> is declared
117 */
118 private boolean isDeclaredInSuperclass(Method m1, Method m2) {
119 boolean fromSuperclass = false;
120 try {
121 Class<?> c1 = classLoader.loadClass(m1.declaringType().name());
122 Class<?> c2 = classLoader.loadClass(m2.declaringType().name());
123 if (c1.isAssignableFrom(c2) && !c1.equals(c2)) {
124 fromSuperclass = true;
125 }
126 } catch (Exception e) {
127 System.err.println(e);
128 }
129 return fromSuperclass;
130 }
131 }