1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package th.co.edge.jseq;
24
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 import java.net.URLClassLoader;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Map;
39
40 import com.sun.jdi.Bootstrap;
41 import com.sun.jdi.VirtualMachine;
42 import com.sun.jdi.connect.AttachingConnector;
43 import com.sun.jdi.connect.Connector;
44 import com.sun.jdi.connect.IllegalConnectorArgumentsException;
45 import com.sun.jdi.connect.LaunchingConnector;
46 import com.sun.jdi.connect.VMStartException;
47 import com.sun.jdi.connect.Connector.Argument;
48
49 public class Main {
50 private static final String PROGRAM_NAME = "JSeq";
51 private static final String PROGRAM_VERSION = "0.5.SNAPSHOT";
52 private static final String[] STANDARD_EXCLUDES =
53 { "java.*", "javax.*", "sun.*", "com.sun.*", "junit.*" };
54 private ActivationList rootActivations = new ActivationList();
55 private ConnectorType connectorType = null;
56 private String attachAddress = null;
57 private String classname = null;
58 private String arguments = null;
59 private String classpath = null;
60 private String readFilename = null;
61 private String saveFilename = null;
62 private String outFilename = null;
63 private Formatter formatter = FormatterRegistry.getInstance().get("svg");
64 private boolean quiet = false;
65 private boolean trace = true;
66 private String startMethod = null;
67 private List<String> includePatterns = new LinkedList<String>();
68 private List<String> excludePatterns = new LinkedList<String>();
69 private boolean stdExcludes = true;
70 private boolean shouldRun = true;
71
72 public static void main(String[] args) {
73 try {
74 final Main main = new Main(args);
75 if (main.shouldRun) {
76
77
78
79 Runtime.getRuntime().addShutdownHook(new Thread() {
80 public void run() {
81 try {
82 main.generateSequenceDiagram();
83 } catch (Exception e) {
84 e.printStackTrace();
85 }
86 }
87 });
88 main.traceProgram();
89
90
91 }
92 } catch (IllegalArgumentException e) {
93 System.err.println(e.getMessage());
94 System.err.println(getUsage());
95 } catch (Exception e) {
96 e.printStackTrace();
97 }
98 }
99
100 private Main(String[] args) {
101 int inx;
102 if (args.length == 0) {
103 throw new IllegalArgumentException("No arguments given");
104 }
105 for (inx = 0; inx < args.length; ++inx) {
106 String arg = args[inx];
107 if (arg.charAt(0) != '-') {
108 break;
109 }
110 if (arg.equals("-connector")) {
111 connectorType = ConnectorType.valueOf(args[++inx]);
112 } else if (arg.equals("-attach")) {
113 attachAddress = args[++inx];
114 } else if (arg.equals("-read")) {
115 readFilename = args[++inx];
116 } else if (arg.equals("-classpath") || arg.equals("-cp")) {
117 classpath = args[++inx];
118 } else if (arg.equals("-save")) {
119 saveFilename = args[++inx];
120 } else if (arg.equals("-out")) {
121 outFilename = args[++inx];
122 } else if (arg.equals("-format")) {
123 formatter = FormatterRegistry.getInstance().get(args[++inx]);
124 } else if (arg.equals("-quiet")) {
125 quiet = true;
126 } else if (arg.equals("-start")) {
127 startMethod = args[++inx];
128 } else if (arg.equals("-include")) {
129 includePatterns.add(args[++inx]);
130 } else if (arg.equals("-exclude")) {
131 excludePatterns.add(args[++inx]);
132 } else if (arg.equals("-nostdexcludes")) {
133 stdExcludes = false;
134 } else if (arg.equals("-notrace")) {
135 trace = false;
136 } else if (arg.equals("-version")) {
137 System.out.println(getVersion());
138 shouldRun = false;
139 } else {
140 throw new IllegalArgumentException("Illegal option: " +
141 args[inx]);
142 }
143 }
144 if (inx < args.length) {
145 classname = args[inx];
146 StringBuffer sb = new StringBuffer();
147 for (++inx; inx < args.length; ++inx) {
148 sb.append(args[inx]);
149 sb.append(' ');
150 }
151 arguments = sb.toString();
152 }
153
154 if (stdExcludes) {
155 excludePatterns = addStandardExcludes(excludePatterns);
156 }
157
158 if (connectorType == null) {
159 if (attachAddress == null) {
160 connectorType = ConnectorType.LAUNCHING;
161 } else {
162 connectorType = ConnectorType.SOCKET;
163 }
164 }
165 }
166
167 private static List<String> addStandardExcludes(
168 List<String> originalExcludes) {
169 List<String> excludes = new LinkedList<String>(originalExcludes);
170 for (String standardExclude : STANDARD_EXCLUDES) {
171 excludes.add(standardExclude);
172 }
173 return excludes;
174 }
175
176 private void traceProgram() throws IOException, ClassNotFoundException {
177 if (readFilename != null) {
178 readActivationList(readFilename);
179 } else if (attachAddress != null) {
180 attachProgram();
181 } else {
182 runProgram();
183 }
184 }
185
186 private void generateSequenceDiagram() throws IOException, FormatException {
187 if (saveFilename != null) {
188 saveActivationList(rootActivations, saveFilename);
189 }
190 if (!quiet) {
191 ActivationList filteredActivations =
192 filterActivations(rootActivations);
193 Diagram diagram = formatter.format(filteredActivations);
194 if (outFilename == null) {
195 System.out.println(diagram);
196 } else {
197 File file = new File(outFilename);
198 diagram.save(file);
199 }
200 }
201 }
202
203 private ActivationList filterActivations(ActivationList activationList) {
204 ActivationList filteredActivations = activationList;
205 if (startMethod != null) {
206 filteredActivations =
207 filteredActivations.find(new MethodFilter(startMethod));
208 }
209 for (String pattern : excludePatterns) {
210 ClassExclusionFilter classExclusionFilter =
211 new ClassExclusionFilter(pattern);
212 filteredActivations =
213 filteredActivations.filter(classExclusionFilter);
214 }
215 filteredActivations =
216 filteredActivations.filter(new ConstructorFilter(
217 getClassLoader()));
218 filteredActivations = filteredActivations.collapseRepetitions();
219
220 return filteredActivations;
221 }
222
223 private ClassLoader getClassLoader() {
224 URLClassLoader classLoader =
225 new URLClassLoader(getClasspathURLs(), ClassLoader
226 .getSystemClassLoader());
227 return classLoader;
228 }
229
230 private URL[] getClasspathURLs() {
231 URL[] classpathURLs = new URL[0];
232 if (classpath != null) {
233 try {
234 String[] classpathEntries = classpath.split("path.separator");
235 classpathURLs = new URL[classpathEntries.length];
236 for (int i = 0; i < classpathURLs.length; i++) {
237 classpathURLs[i] =
238 new File(classpathEntries[i]).toURI().toURL();
239 }
240 } catch (MalformedURLException e) {
241 System.err.println(e);
242 }
243 }
244 return classpathURLs;
245 }
246
247 private void readActivationList(String filename) throws IOException,
248 ClassNotFoundException {
249 ObjectInputStream in =
250 new ObjectInputStream(new FileInputStream(filename));
251 rootActivations = (ActivationList) in.readObject();
252 in.close();
253 }
254
255 private void saveActivationList(ActivationList activationList,
256 String filename) throws IOException {
257 ObjectOutputStream out =
258 new ObjectOutputStream(new FileOutputStream(filename));
259 out.writeObject(activationList);
260 out.close();
261 }
262
263 private void attachProgram() {
264 ProgramRunner runner =
265 new ProgramRunner(rootActivations, attachAddress,
266 includePatterns, excludePatterns, startMethod, trace);
267 runner.runProgram(connectorType);
268 }
269
270 private void runProgram() {
271 ProgramRunner runner =
272 new ProgramRunner(rootActivations, classname, arguments,
273 classpath, includePatterns, excludePatterns,
274 startMethod, trace);
275 runner.runProgram(connectorType);
276 }
277
278 public static String getUsage() {
279 return "Usage: jseq [-options] <class> [args...]\n"
280 + "\t(to execute a class)\n"
281 + " or jseq [-options] -attach <address>\n"
282 + "\t(to attach to a running VM at the specified address)\n"
283 + " or jseq [-options] -read <filename>\n"
284 + "\t(to generate output from a previously saved run)\n"
285 + "\n"
286 + "Options for running a program:\n"
287 + "\t[-classpath <path>]\tto set classpath\n"
288 + "\t[-cp <path>]\tsame as -classpath\n"
289 + "\t[-save <filename>]\tto save the program run in a file\n"
290 + "\n"
291 + "Options for attaching to a program:\n"
292 + "\t[-connector {SOCKET,SHARED_MEMORY}]\tto choose JDI connector\n"
293 + "\n"
294 + "Options for generating sequence diagrams:\n"
295 + "\t[-out <filename>]\tto save diagram in a file\n"
296 + "\t[-format {text,png,sdedit,svg,argouml}]\tto specify format of output\n"
297 + "\t[-quiet]\tto not generate any output\n"
298 + "\t[-start <methodname>]\tto specify start method in diagram\n"
299 + "\t[-include <class regexp>]\tto include only some classes in diagram\n"
300 + "\t[-exclude <class regexp>]\tto exclude some classes from diagram\n"
301 + "\t[-nostdexcludes]\tto not exclude java.*, javax.*, etc\n"
302 + "\n" + "Other options:\n"
303 + "\t[-notrace]\tto turn off tracing of method entries, etc.\n"
304 + "\t[-version]\tto print version information and exit";
305 }
306
307 public static String getVersion() {
308 return PROGRAM_NAME + " " + PROGRAM_VERSION;
309 }
310
311 private static class ProgramRunner {
312 private ActivationList rootActivations;
313 private String classname;
314 private String arguments;
315 private String classpath;
316 private String attachAddress;
317 private String startMethod;
318 private boolean trace;
319 private VirtualMachine vm;
320 private Thread errThread;
321 private Thread outThread;
322 private List<String> includes;
323 private List<String> excludes;
324 private EventThread eventThread;
325
326 public ProgramRunner(ActivationList rootActivations,
327 String attachAddress, List<String> includes,
328 List<String> excludes, String startMethod, boolean trace) {
329 this.rootActivations = rootActivations;
330 this.attachAddress = attachAddress;
331 this.includes = includes;
332 this.excludes = excludes;
333 this.startMethod = startMethod;
334 this.trace = trace;
335 this.classpath = System.getProperty("java.class.path");
336 }
337
338 public ProgramRunner(ActivationList rootActivations, String classname,
339 String arguments, String classpath, List<String> includes,
340 List<String> excludes, String startMethod, boolean trace) {
341 this.rootActivations = rootActivations;
342 this.classname = classname;
343 this.arguments = arguments;
344 this.includes = includes;
345 this.excludes = excludes;
346 this.startMethod = startMethod;
347 this.trace = trace;
348 if (classpath == null) {
349 this.classpath = System.getProperty("java.class.path");
350 } else {
351 this.classpath = classpath;
352 }
353 }
354
355 public void runProgram(ConnectorType connectorType) {
356 if (attachAddress == null) {
357 vm = launchTarget(connectorType, classname + " " + arguments);
358 redirectOutput();
359 } else {
360 vm = attachTarget(connectorType, attachAddress);
361 }
362 List<String> emptyStringList = new ArrayList<String>();
363 eventThread =
364 new EventThread(vm, rootActivations, includes, excludes,
365 emptyStringList, trace);
366 eventThread.setEventRequests(startMethod);
367 eventThread.start();
368 vm.resume();
369
370 try {
371 eventThread.join();
372 if (attachAddress == null) {
373 errThread.join();
374 outThread.join();
375 }
376 } catch (InterruptedException exc) {
377
378 }
379 }
380
381 private VirtualMachine attachTarget(ConnectorType connectorType,
382 String attachAddress) {
383 AttachingConnector connector =
384 (AttachingConnector) findConnectorByType(connectorType);
385 Map<String, String> arguments = new HashMap<String, String>();
386 switch (connectorType) {
387 case SOCKET:
388 String[] hostAndPort = attachAddress.split(":");
389 if (hostAndPort.length != 2) {
390 throw new IllegalArgumentException(
391 "Attach address should be formatted as 'hostname:port'");
392 }
393 arguments.put("hostname", hostAndPort[0]);
394 arguments.put("port", hostAndPort[1]);
395 break;
396 case SHARED_MEMORY:
397 arguments.put("name", attachAddress);
398 break;
399 default:
400 throw new IllegalArgumentException(
401 "Unexpected connector type: " + connectorType);
402 }
403 Map<String, Argument> connectorArguments =
404 getConnectorArgs(connector, arguments);
405 try {
406 return connector.attach(connectorArguments);
407 } catch (IOException e) {
408 throw new Error("Unable to launch target VM: " + e);
409 } catch (IllegalConnectorArgumentsException e) {
410 throw new Error("Internal error: " + e);
411 }
412 }
413
414 private VirtualMachine launchTarget(ConnectorType connectorType,
415 String mainArgs) {
416 LaunchingConnector connector =
417 (LaunchingConnector) findConnectorByType(connectorType);
418 Map<String, String> arguments = new HashMap<String, String>();
419 switch (connectorType) {
420 case LAUNCHING:
421 arguments.put("main", mainArgs);
422 break;
423 default:
424 throw new IllegalArgumentException(
425 "Unexpected connector type: " + connectorType);
426 }
427 Map<String, Argument> connectorArguments =
428 getConnectorArgs(connector, arguments);
429 Connector.StringArgument options =
430 (Connector.StringArgument) connectorArguments
431 .get("options");
432 options.setValue(options.value() + "-classpath " + classpath);
433 try {
434 return connector.launch(connectorArguments);
435 } catch (IOException exc) {
436 throw new Error("Unable to launch target VM: " + exc);
437 } catch (IllegalConnectorArgumentsException exc) {
438 throw new Error("Internal error: " + exc);
439 } catch (VMStartException exc) {
440 throw new Error("Target VM failed to initialize: " +
441 exc.getMessage());
442 }
443 }
444
445 private Connector findConnectorByType(ConnectorType connectorType) {
446 Connector foundConnector = null;
447 String name = connectorType.getName();
448 List<Connector> connectors =
449 Bootstrap.virtualMachineManager().allConnectors();
450 for (Connector connector : connectors) {
451 if (connector.name().equals(name)) {
452 foundConnector = connector;
453 break;
454 }
455 }
456 if (foundConnector == null) {
457 throw new Error("Connector not found: " + name);
458 }
459 return foundConnector;
460 }
461
462 private Map<String, Argument> getConnectorArgs(Connector connector,
463 Map<String, String> arguments) {
464 Map<String, Argument> allArguments = connector.defaultArguments();
465 for (String argumentName : arguments.keySet()) {
466 String argumentValue = arguments.get(argumentName);
467 Connector.Argument argument = allArguments.get(argumentName);
468 if (argument == null) {
469 throw new IllegalArgumentException(
470 "Unknown argument name '" + argumentName +
471 "' for " + connector);
472 }
473 argument.setValue(argumentValue);
474 }
475 return allArguments;
476 }
477
478 private void redirectOutput() {
479 Process process = vm.process();
480 errThread =
481 new StreamRedirectThread("error reader", process
482 .getErrorStream(), System.err);
483 outThread =
484 new StreamRedirectThread("output reader", process
485 .getInputStream(), System.out);
486 errThread.start();
487 outThread.start();
488 }
489 }
490
491 private static enum ConnectorType {
492 SOCKET("com.sun.jdi.SocketAttach", true),
493 SHARED_MEMORY("com.sun.jdi.SharedMemoryAttach", true),
494 LAUNCHING("com.sun.jdi.CommandLineLaunch", false);
495
496 private String name;
497 private boolean attaching;
498
499 private ConnectorType(String name, boolean attaching) {
500 this.name = name;
501 this.attaching = attaching;
502 }
503
504 public String getName() {
505 return name;
506 }
507
508 public boolean isAttaching() {
509 return attaching;
510 }
511 }
512 }