Coverage Report - th.co.edge.jseq.Main
 
Classes in this File Line Coverage Branch Coverage Complexity
Main
0%
0/139
0%
0/68
0
Main$1
0%
0/6
N/A
0
Main$2
0%
0/1
N/A
0
Main$ConnectorType
0%
0/10
N/A
0
Main$ProgramRunner
0%
0/96
0%
0/23
0
 
 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 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  0
 public class Main {
 50  
     private static final String PROGRAM_NAME = "JSeq";
 51  
     private static final String PROGRAM_VERSION = "0.5.SNAPSHOT";
 52  0
     private static final String[] STANDARD_EXCLUDES =
 53  
             { "java.*", "javax.*", "sun.*", "com.sun.*", "junit.*" };
 54  0
     private ActivationList rootActivations = new ActivationList();
 55  0
     private ConnectorType connectorType = null;
 56  0
     private String attachAddress = null;
 57  0
     private String classname = null;
 58  0
     private String arguments = null;
 59  0
     private String classpath = null;
 60  0
     private String readFilename = null;
 61  0
     private String saveFilename = null;
 62  0
     private String outFilename = null;
 63  0
     private Formatter formatter = FormatterRegistry.getInstance().get("svg");
 64  0
     private boolean quiet = false;
 65  0
     private boolean trace = true;
 66  0
     private String startMethod = null;
 67  0
     private List<String> includePatterns = new LinkedList<String>();
 68  0
     private List<String> excludePatterns = new LinkedList<String>();
 69  0
     private boolean stdExcludes = true;
 70  0
     private boolean shouldRun = true;
 71  
 
 72  
     public static void main(String[] args) {
 73  
         try {
 74  0
             final Main main = new Main(args);
 75  0
             if (main.shouldRun) {
 76  
                 // Make sure that generateSequenceDiagram is always called at
 77  
                 // the end. This way, it is possible to attach to a long-running
 78  
                 // process and use Ctrl-C to stop JSeq and still get a diagram.
 79  0
                 Runtime.getRuntime().addShutdownHook(new Thread() {
 80  
                     public void run() {
 81  
                         try {
 82  0
                             main.generateSequenceDiagram();
 83  0
                         } catch (Exception e) {
 84  0
                             e.printStackTrace();
 85  0
                         }
 86  0
                     }
 87  
                 });
 88  0
                 main.traceProgram();
 89  
                 // Here is main.generateSequenceDiagram called by the shutdown
 90  
                 // hook.
 91  
             }
 92  0
         } catch (IllegalArgumentException e) {
 93  0
             System.err.println(e.getMessage());
 94  0
             System.err.println(getUsage());
 95  0
         } catch (Exception e) {
 96  0
             e.printStackTrace();
 97  0
         }
 98  0
     }
 99  
 
 100  0
     private Main(String[] args) {
 101  
         int inx;
 102  0
         if (args.length == 0) {
 103  0
             throw new IllegalArgumentException("No arguments given");
 104  
         }
 105  0
         for (inx = 0; inx < args.length; ++inx) {
 106  0
             String arg = args[inx];
 107  0
             if (arg.charAt(0) != '-') {
 108  0
                 break;
 109  
             }
 110  0
             if (arg.equals("-connector")) {
 111  0
                 connectorType = ConnectorType.valueOf(args[++inx]);
 112  0
             } else if (arg.equals("-attach")) {
 113  0
                 attachAddress = args[++inx];
 114  0
             } else if (arg.equals("-read")) {
 115  0
                 readFilename = args[++inx];
 116  0
             } else if (arg.equals("-classpath") || arg.equals("-cp")) {
 117  0
                 classpath = args[++inx];
 118  0
             } else if (arg.equals("-save")) {
 119  0
                 saveFilename = args[++inx];
 120  0
             } else if (arg.equals("-out")) {
 121  0
                 outFilename = args[++inx];
 122  0
             } else if (arg.equals("-format")) {
 123  0
                 formatter = FormatterRegistry.getInstance().get(args[++inx]);
 124  0
             } else if (arg.equals("-quiet")) {
 125  0
                 quiet = true;
 126  0
             } else if (arg.equals("-start")) {
 127  0
                 startMethod = args[++inx];
 128  0
             } else if (arg.equals("-include")) {
 129  0
                 includePatterns.add(args[++inx]);
 130  0
             } else if (arg.equals("-exclude")) {
 131  0
                 excludePatterns.add(args[++inx]);
 132  0
             } else if (arg.equals("-nostdexcludes")) {
 133  0
                 stdExcludes = false;
 134  0
             } else if (arg.equals("-notrace")) {
 135  0
                 trace = false;
 136  0
             } else if (arg.equals("-version")) {
 137  0
                 System.out.println(getVersion());
 138  0
                 shouldRun = false;
 139  
             } else {
 140  0
                 throw new IllegalArgumentException("Illegal option: " +
 141  
                         args[inx]);
 142  
             }
 143  
         }
 144  0
         if (inx < args.length) {
 145  0
             classname = args[inx];
 146  0
             StringBuffer sb = new StringBuffer();
 147  0
             for (++inx; inx < args.length; ++inx) {
 148  0
                 sb.append(args[inx]);
 149  0
                 sb.append(' ');
 150  
             }
 151  0
             arguments = sb.toString();
 152  
         }
 153  
 
 154  0
         if (stdExcludes) {
 155  0
             excludePatterns = addStandardExcludes(excludePatterns);
 156  
         }
 157  
 
 158  0
         if (connectorType == null) {
 159  0
             if (attachAddress == null) {
 160  0
                 connectorType = ConnectorType.LAUNCHING;
 161  
             } else {
 162  0
                 connectorType = ConnectorType.SOCKET;
 163  
             }
 164  
         }
 165  0
     }
 166  
 
 167  
     private static List<String> addStandardExcludes(
 168  
             List<String> originalExcludes) {
 169  0
         List<String> excludes = new LinkedList<String>(originalExcludes);
 170  0
         for (String standardExclude : STANDARD_EXCLUDES) {
 171  0
             excludes.add(standardExclude);
 172  
         }
 173  0
         return excludes;
 174  
     }
 175  
 
 176  
     private void traceProgram() throws IOException, ClassNotFoundException {
 177  0
         if (readFilename != null) {
 178  0
             readActivationList(readFilename);
 179  0
         } else if (attachAddress != null) {
 180  0
             attachProgram();
 181  
         } else {
 182  0
             runProgram();
 183  
         }
 184  0
     }
 185  
 
 186  
     private void generateSequenceDiagram() throws IOException, FormatException {
 187  0
         if (saveFilename != null) {
 188  0
             saveActivationList(rootActivations, saveFilename);
 189  
         }
 190  0
         if (!quiet) {
 191  0
             ActivationList filteredActivations =
 192  
                     filterActivations(rootActivations);
 193  0
             Diagram diagram = formatter.format(filteredActivations);
 194  0
             if (outFilename == null) {
 195  0
                 System.out.println(diagram);
 196  
             } else {
 197  0
                 File file = new File(outFilename);
 198  0
                 diagram.save(file);
 199  
             }
 200  
         }
 201  0
     }
 202  
 
 203  
     private ActivationList filterActivations(ActivationList activationList) {
 204  0
         ActivationList filteredActivations = activationList;
 205  0
         if (startMethod != null) {
 206  0
             filteredActivations =
 207  
                     filteredActivations.find(new MethodFilter(startMethod));
 208  
         }
 209  0
         for (String pattern : excludePatterns) {
 210  0
             ClassExclusionFilter classExclusionFilter =
 211  
                     new ClassExclusionFilter(pattern);
 212  0
             filteredActivations =
 213  
                     filteredActivations.filter(classExclusionFilter);
 214  0
         }
 215  0
         filteredActivations =
 216  
                 filteredActivations.filter(new ConstructorFilter(
 217  
                         getClassLoader()));
 218  0
         filteredActivations = filteredActivations.collapseRepetitions();
 219  
 
 220  0
         return filteredActivations;
 221  
     }
 222  
 
 223  
     private ClassLoader getClassLoader() {
 224  0
         URLClassLoader classLoader =
 225  
                 new URLClassLoader(getClasspathURLs(), ClassLoader
 226  
                         .getSystemClassLoader());
 227  0
         return classLoader;
 228  
     }
 229  
 
 230  
     private URL[] getClasspathURLs() {
 231  0
         URL[] classpathURLs = new URL[0];
 232  0
         if (classpath != null) {
 233  
             try {
 234  0
                 String[] classpathEntries = classpath.split("path.separator");
 235  0
                 classpathURLs = new URL[classpathEntries.length];
 236  0
                 for (int i = 0; i < classpathURLs.length; i++) {
 237  0
                     classpathURLs[i] =
 238  
                             new File(classpathEntries[i]).toURI().toURL();
 239  
                 }
 240  0
             } catch (MalformedURLException e) {
 241  0
                 System.err.println(e);
 242  0
             }
 243  
         }
 244  0
         return classpathURLs;
 245  
     }
 246  
 
 247  
     private void readActivationList(String filename) throws IOException,
 248  
             ClassNotFoundException {
 249  0
         ObjectInputStream in =
 250  
                 new ObjectInputStream(new FileInputStream(filename));
 251  0
         rootActivations = (ActivationList) in.readObject();
 252  0
         in.close();
 253  0
     }
 254  
 
 255  
     private void saveActivationList(ActivationList activationList,
 256  
             String filename) throws IOException {
 257  0
         ObjectOutputStream out =
 258  
                 new ObjectOutputStream(new FileOutputStream(filename));
 259  0
         out.writeObject(activationList);
 260  0
         out.close();
 261  0
     }
 262  
 
 263  
     private void attachProgram() {
 264  0
         ProgramRunner runner =
 265  
                 new ProgramRunner(rootActivations, attachAddress,
 266  
                         includePatterns, excludePatterns, startMethod, trace);
 267  0
         runner.runProgram(connectorType);
 268  0
     }
 269  
 
 270  
     private void runProgram() {
 271  0
         ProgramRunner runner =
 272  
                 new ProgramRunner(rootActivations, classname, arguments,
 273  
                         classpath, includePatterns, excludePatterns,
 274  
                         startMethod, trace);
 275  0
         runner.runProgram(connectorType);
 276  0
     }
 277  
 
 278  
     public static String getUsage() {
 279  0
         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  0
         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  0
                 List<String> excludes, String startMethod, boolean trace) {
 329  0
             this.rootActivations = rootActivations;
 330  0
             this.attachAddress = attachAddress;
 331  0
             this.includes = includes;
 332  0
             this.excludes = excludes;
 333  0
             this.startMethod = startMethod;
 334  0
             this.trace = trace;
 335  0
             this.classpath = System.getProperty("java.class.path");
 336  0
         }
 337  
 
 338  
         public ProgramRunner(ActivationList rootActivations, String classname,
 339  
                 String arguments, String classpath, List<String> includes,
 340  0
                 List<String> excludes, String startMethod, boolean trace) {
 341  0
             this.rootActivations = rootActivations;
 342  0
             this.classname = classname;
 343  0
             this.arguments = arguments;
 344  0
             this.includes = includes;
 345  0
             this.excludes = excludes;
 346  0
             this.startMethod = startMethod;
 347  0
             this.trace = trace;
 348  0
             if (classpath == null) {
 349  0
                 this.classpath = System.getProperty("java.class.path");
 350  
             } else {
 351  0
                 this.classpath = classpath;
 352  
             }
 353  0
         }
 354  
 
 355  
         public void runProgram(ConnectorType connectorType) {
 356  0
             if (attachAddress == null) {
 357  0
                 vm = launchTarget(connectorType, classname + " " + arguments);
 358  0
                 redirectOutput();
 359  
             } else {
 360  0
                 vm = attachTarget(connectorType, attachAddress);
 361  
             }
 362  0
             List<String> emptyStringList = new ArrayList<String>();
 363  0
             eventThread =
 364  
                     new EventThread(vm, rootActivations, includes, excludes,
 365  
                             emptyStringList, trace);
 366  0
             eventThread.setEventRequests(startMethod);
 367  0
             eventThread.start();
 368  0
             vm.resume();
 369  
 
 370  
             try {
 371  0
                 eventThread.join();
 372  0
                 if (attachAddress == null) {
 373  0
                     errThread.join();
 374  0
                     outThread.join();
 375  
                 }
 376  0
             } catch (InterruptedException exc) {
 377  
                 // Ignore
 378  0
             }
 379  0
         }
 380  
 
 381  
         private VirtualMachine attachTarget(ConnectorType connectorType,
 382  
                 String attachAddress) {
 383  0
             AttachingConnector connector =
 384  
                     (AttachingConnector) findConnectorByType(connectorType);
 385  0
             Map<String, String> arguments = new HashMap<String, String>();
 386  0
             switch (connectorType) {
 387  
             case SOCKET:
 388  0
                 String[] hostAndPort = attachAddress.split(":");
 389  0
                 if (hostAndPort.length != 2) {
 390  0
                     throw new IllegalArgumentException(
 391  
                             "Attach address should be formatted as 'hostname:port'");
 392  
                 }
 393  0
                 arguments.put("hostname", hostAndPort[0]);
 394  0
                 arguments.put("port", hostAndPort[1]);
 395  0
                 break;
 396  
             case SHARED_MEMORY:
 397  0
                 arguments.put("name", attachAddress);
 398  0
                 break;
 399  
             default:
 400  0
                 throw new IllegalArgumentException(
 401  
                         "Unexpected connector type: " + connectorType);
 402  
             }
 403  0
             Map<String, Argument> connectorArguments =
 404  
                     getConnectorArgs(connector, arguments);
 405  
             try {
 406  0
                 return connector.attach(connectorArguments);
 407  0
             } catch (IOException e) {
 408  0
                 throw new Error("Unable to launch target VM: " + e);
 409  0
             } catch (IllegalConnectorArgumentsException e) {
 410  0
                 throw new Error("Internal error: " + e);
 411  
             }
 412  
         }
 413  
 
 414  
         private VirtualMachine launchTarget(ConnectorType connectorType,
 415  
                 String mainArgs) {
 416  0
             LaunchingConnector connector =
 417  
                     (LaunchingConnector) findConnectorByType(connectorType);
 418  0
             Map<String, String> arguments = new HashMap<String, String>();
 419  0
             switch (connectorType) {
 420  
             case LAUNCHING:
 421  0
                 arguments.put("main", mainArgs);
 422  0
                 break;
 423  
             default:
 424  0
                 throw new IllegalArgumentException(
 425  
                         "Unexpected connector type: " + connectorType);
 426  
             }
 427  0
             Map<String, Argument> connectorArguments =
 428  
                     getConnectorArgs(connector, arguments);
 429  0
             Connector.StringArgument options =
 430  
                     (Connector.StringArgument) connectorArguments
 431  
                             .get("options");
 432  0
             options.setValue(options.value() + "-classpath " + classpath);
 433  
             try {
 434  0
                 return connector.launch(connectorArguments);
 435  0
             } catch (IOException exc) {
 436  0
                 throw new Error("Unable to launch target VM: " + exc);
 437  0
             } catch (IllegalConnectorArgumentsException exc) {
 438  0
                 throw new Error("Internal error: " + exc);
 439  0
             } catch (VMStartException exc) {
 440  0
                 throw new Error("Target VM failed to initialize: " +
 441  
                         exc.getMessage());
 442  
             }
 443  
         }
 444  
 
 445  
         private Connector findConnectorByType(ConnectorType connectorType) {
 446  0
             Connector foundConnector = null;
 447  0
             String name = connectorType.getName();
 448  0
             List<Connector> connectors =
 449  
                     Bootstrap.virtualMachineManager().allConnectors();
 450  0
             for (Connector connector : connectors) {
 451  0
                 if (connector.name().equals(name)) {
 452  0
                     foundConnector = connector;
 453  0
                     break;
 454  
                 }
 455  
             }
 456  0
             if (foundConnector == null) {
 457  0
                 throw new Error("Connector not found: " + name);
 458  
             }
 459  0
             return foundConnector;
 460  
         }
 461  
 
 462  
         private Map<String, Argument> getConnectorArgs(Connector connector,
 463  
                 Map<String, String> arguments) {
 464  0
             Map<String, Argument> allArguments = connector.defaultArguments();
 465  0
             for (String argumentName : arguments.keySet()) {
 466  0
                 String argumentValue = arguments.get(argumentName);
 467  0
                 Connector.Argument argument = allArguments.get(argumentName);
 468  0
                 if (argument == null) {
 469  0
                     throw new IllegalArgumentException(
 470  
                             "Unknown argument name '" + argumentName +
 471  
                                     "' for " + connector);
 472  
                 }
 473  0
                 argument.setValue(argumentValue);
 474  0
             }
 475  0
             return allArguments;
 476  
         }
 477  
 
 478  
         private void redirectOutput() {
 479  0
             Process process = vm.process();
 480  0
             errThread =
 481  
                     new StreamRedirectThread("error reader", process
 482  
                             .getErrorStream(), System.err);
 483  0
             outThread =
 484  
                     new StreamRedirectThread("output reader", process
 485  
                             .getInputStream(), System.out);
 486  0
             errThread.start();
 487  0
             outThread.start();
 488  0
         }
 489  
     }
 490  
 
 491  0
     private static enum ConnectorType {
 492  0
         SOCKET("com.sun.jdi.SocketAttach", true),
 493  0
         SHARED_MEMORY("com.sun.jdi.SharedMemoryAttach", true),
 494  0
         LAUNCHING("com.sun.jdi.CommandLineLaunch", false);
 495  
 
 496  
         private String name;
 497  
         private boolean attaching;
 498  
 
 499  0
         private ConnectorType(String name, boolean attaching) {
 500  0
             this.name = name;
 501  0
             this.attaching = attaching;
 502  0
         }
 503  
 
 504  
         public String getName() {
 505  0
             return name;
 506  
         }
 507  
 
 508  
         public boolean isAttaching() {
 509  0
             return attaching;
 510  
         }
 511  
     }
 512  
 }