1 // The following classes are from the Java Debugger Platform Architecture jpda 2 // The ship with SDK 1.3+ in tools.jar 3 import com.sun.jdi.VirtualMachine; 4 import com.sun.jdi.Bootstrap; 5 import com.sun.jdi.Method; 6 import com.sun.jdi.ObjectReference; 7 import com.sun.jdi.ReferenceType; 8 import com.sun.jdi.ThreadReference; 9 import com.sun.jdi.StackFrame; 10 import com.sun.jdi.connect.Connector; 11 import com.sun.jdi.connect.LaunchingConnector; 12 13 import com.sun.jdi.event.Event; 14 import com.sun.jdi.event.MethodEntryEvent; 15 import com.sun.jdi.event.MethodExitEvent; 16 import com.sun.jdi.request.EventRequestManager; 17 import com.sun.jdi.request.MethodEntryRequest; 18 import com.sun.jdi.request.MethodExitRequest; 19 20 // IO classes 21 import java.io.BufferedReader; 22 import java.io.FileReader; 23 import java.io.FileOutputStream; 24 import java.io.InputStreamReader; 25 import java.io.InputStream; 26 import java.io.IOException; 27 import java.io.PrintStream; 28 29 // util classes 30 import java.util.ArrayList; 31 import java.util.Iterator; 32 import java.util.List; 33 import java.util.HashMap; 34 import java.util.Map; 35 import java.util.Set; 36 37 /** 38 * This class produces outlines of call sequences for other Java programs 39 * by using debugger hooks. It is useful for building UML sequence diagrams. 40 * See the documentation for {@link #main} for usage. See UML::Sequence 41 * on CPAN for Perl scripts to make the diagrams. In particular, see 42 * genericseq.pl and UML::Sequence::JavaSeq.pm. 43 */ 44 public class Seq { 45 VirtualMachine vm; 46 Process process; 47 SeqEventHandler handler; 48 EventRequestManager requestManager; 49 MethodEntryRequest initialEntryRequest; 50 MethodEntryRequest regularEntryRequest; 51 MethodExitRequest regularExitRequest; 52 static final int INITIAL_EVENT_STATUS = 1; 53 static final int REGULAR_EVENT_STATUS = 2; 54 int eventStatus = INITIAL_EVENT_STATUS; 55 int indent = 0; 56 boolean everythingIsInteresting = false; 57 HashMap interestingMethods; 58 ArrayList interestingClasses; 59 PrintStream outputStream; 60 String excludeFilter; 61 62 // objects is keyed by object hash code storing object's number. 63 // Numbers are per class and are issued sequentially from 1 during 64 // constructor processing. 65 HashMap objects = new HashMap(); 66 // nextObjectNumber is keyed by class name storing the most recently 67 // used number. Preincrement this to use it. 68 HashMap nextObjectNumber = new HashMap(); 69 70 71 /** 72 * Most callers will use only this method. 73 * Builds a connection to the debugger, launches the supplied program, 74 * directs production outpuer. 75 * @param interestingMethodsFile name of a file listing methods or classes 76 * you want to see in your output 77 * see <a href="Hello.methods">Hello.methods</a> for an example with 78 * documenation 79 * @param outputFileName name of a file where output will go, standard out 80 * can't be used, since the program you are tracing 81 * might be using it 82 * @param args the java class to invoke and any command line arguments 83 * it needs 84 */ Seq( String interestingMethodsFile, String outputFileName, String[] args )85 public Seq( 86 String interestingMethodsFile, 87 String outputFileName, 88 String[] args 89 ) throws Exception { 90 setupInterestingThings(interestingMethodsFile); 91 openOutput(outputFileName); 92 93 LaunchingConnector conn = findConnector(); 94 vm = conn.launch(getArgsMap(conn, args)); 95 process = vm.process(); 96 dumpProcessOutput(process); 97 handler = new SeqEventHandler(this); 98 requestManager = vm.eventRequestManager(); 99 initialEntryRequest = requestManager.createMethodEntryRequest(); 100 101 // wait for a method in the starting class 102 initialEntryRequest.addClassFilter(args[0]); 103 initialEntryRequest.enable(); 104 } 105 setupInterestingThings(String file)106 private void setupInterestingThings(String file) throws IOException { 107 interestingMethods = new HashMap(); 108 HashMap classHash = new HashMap(); 109 interestingClasses = new ArrayList(); 110 FileReader fr = new FileReader(file); 111 BufferedReader reader = new BufferedReader(fr); 112 113 Integer dummyInt = new Integer(1); 114 String line; 115 while ((line = reader.readLine()) != null) { 116 // skip comments and blanks 117 if (line.startsWith("#") || line.trim().length() == 0) { 118 continue; 119 } 120 // turn off method name checking if file has an 'ALL' line 121 else if (line.equals("ALL")) { 122 everythingIsInteresting = true; 123 } 124 // assume everything else is a method/class name 125 // There is no harm in putting extraneous things into the 126 // interestingMethods hash. If everythingIsInteresting, 127 // the hash will be completely ignored. In other cases, 128 // actual methods are looked up in the hash, if they are there 129 // they print, otherwise not. No one cares if extra entries 130 // are there. 131 else { 132 interestingMethods.put(line, dummyInt); 133 classHash .put(grabClassName(line), dummyInt); 134 } 135 } 136 reader.close(); 137 fr.close(); 138 139 // turn classHash into a List, this could be folded into 140 // switchToRegularStatus 141 Set classKeys = classHash.keySet(); 142 Iterator iter = classKeys.iterator(); 143 while (iter.hasNext()) { 144 interestingClasses.add(iter.next()); 145 } 146 } 147 grabClassName(String line)148 private String grabClassName(String line) { 149 // look for opening ( 150 int parenPos = line.indexOf('('); 151 // there is one, this is a signature 152 if (parenPos >= 0) { 153 // remove arg list 154 String methodName = line.substring(0, parenPos); 155 // look for last dot 156 int lastDot = methodName.lastIndexOf('.'); 157 // there is one, there is a package name 158 if (lastDot >= 0) { 159 // remove the method name, leaving all packages and the class 160 String className = methodName.substring(0, lastDot); 161 return className; 162 } 163 else { 164 return methodName; // unlikely 165 } 166 } 167 // no opening paren, the whole line is a class name 168 else { 169 return line; 170 } 171 } 172 openOutput(String file)173 private void openOutput(String file) throws IOException { 174 FileOutputStream fos = new FileOutputStream(file); 175 outputStream = new PrintStream(fos); 176 } 177 getVM()178 public VirtualMachine getVM() { return vm; } 179 180 // sets the main attribute of the connection argument hash to the 181 // name of the program to trace, concatenated with its arguments, 182 // the list is delimited by spaces getArgsMap(Connector conn, String[] args)183 private Map getArgsMap(Connector conn, String[] args) { 184 Map argsMap = conn.defaultArguments(); 185 Connector.Argument mainArg = (Connector.Argument)argsMap.get("main"); 186 187 StringBuffer sb = new StringBuffer(); 188 189 int argCount = args.length; 190 int maxArg = argCount - 1; 191 for (int i = 0; i < argCount; i++) { 192 sb.append(args[i]); 193 if (i < maxArg) { 194 sb.append(" "); 195 } 196 } 197 198 mainArg.setValue(sb.toString()); 199 return argsMap; 200 } 201 202 // gains a valid LaunchingConnector reference by a name lookup findConnector()203 public LaunchingConnector findConnector() { 204 List connectors = Bootstrap.virtualMachineManager().allConnectors(); 205 Iterator iter = connectors.iterator(); 206 while (iter.hasNext()) { 207 Connector conn = (Connector)iter.next(); 208 if (conn.name().equals("com.sun.jdi.CommandLineLaunch")) { 209 return (LaunchingConnector)conn; 210 } 211 } 212 return null; 213 } 214 215 // when a method exits, adjusts the depth of the call sequence 216 // and restarts the virtual machine methodExitEvent(MethodExitEvent event)217 public void methodExitEvent(MethodExitEvent event) { 218 indent--; 219 vm.resume(); 220 } 221 222 // the virtual machine is up, ask it to start vmStartEvent(Event event)223 public void vmStartEvent(Event event) { 224 vm.resume(); 225 } 226 227 // If this is the first entry event, swithToRegularStatus. 228 // In all cases, print the method signature, if the 229 // user is interested in it. Then increment the call sequence depth 230 // and restart the virtual machine. methodEntryEvent(MethodEntryEvent event)231 public void methodEntryEvent(MethodEntryEvent event) { 232 if (eventStatus == INITIAL_EVENT_STATUS) { 233 switchToRegularStatus(); 234 } 235 236 Method method = event.method(); 237 String signature = grabSignature(method); 238 String objectName = grabInstanceName(event, method); 239 240 Object includeIt = interestingMethods.get(signature); 241 242 if (everythingIsInteresting || includeIt != null) { 243 outputStream.println(formIndentString() + objectName + signature); 244 } 245 246 indent++; 247 vm.resume(); 248 } 249 250 // Returns the manufactured name of the instance which is operative 251 // in the current method (the one called this in that method). 252 // Maintains the list of objects by number using two hashes: 253 // objects and nextObjectNumber. For example the second Roller object 254 // used in a program will yield roller2. grabInstanceName(MethodEntryEvent event, Method method)255 private String grabInstanceName(MethodEntryEvent event, Method method) { 256 ObjectReference thisRef = grabStackTopReference(event); 257 258 if (thisRef == null) { // the top method is static => no this instance 259 return ""; 260 } 261 262 String type = thisRef.referenceType().name(); 263 Integer thisCode = new Integer(thisRef.hashCode()); 264 Integer countI = null; 265 266 if (method.isConstructor()) { // store the hash code 267 countI = (Integer)nextObjectNumber.get(type); 268 int count = 0; 269 if (countI == null) { count = 1; } 270 else { count = countI.intValue() + 1; } 271 272 countI = new Integer(count); 273 nextObjectNumber.put(type, countI); 274 275 objects.put(thisCode, countI); 276 } 277 else { // regular instance method (not constructor, not static) 278 countI = (Integer)objects.get(thisCode); 279 } 280 return lcfirst(type) + countI.toString() + ":"; 281 } 282 283 // Examines the current stack frame returning the ObjectReference 284 // of the instance operative in the method on top of the stack. 285 // Caller can fish in the returned reference for the type name of 286 // the operative instance grabStackTopReference(MethodEntryEvent event)287 private ObjectReference grabStackTopReference(MethodEntryEvent event) { 288 ThreadReference thread = event.thread(); 289 StackFrame frame = null; 290 try { 291 frame = thread.frame(0); 292 } catch (Exception e) { } 293 // com.sun.jdi.IncompatibleThreadStateException 294 295 return frame.thisObject(); 296 } 297 298 // This should be part of java.lang.String. It takes a String and 299 // returns it with the first character in lower case Roller becomes roller. lcfirst(String in)300 private static String lcfirst(String in) { 301 String first = in.substring(0, 1); 302 String rest = in.substring(1); 303 return first.toLowerCase() + rest; 304 } 305 306 // turn off initial entry request 307 // make new entry and exit requests for each class the user want to see switchToRegularStatus()308 private void switchToRegularStatus() { 309 eventStatus = REGULAR_EVENT_STATUS; 310 initialEntryRequest.disable(); 311 312 Iterator iter = interestingClasses.iterator(); 313 if (iter.hasNext()) { 314 while (iter.hasNext()) { // make one filter for each class 315 String className = (String)iter.next(); 316 317 MethodEntryRequest entryRequest; 318 MethodExitRequest exitRequest; 319 320 entryRequest = requestManager.createMethodEntryRequest(); 321 exitRequest = requestManager.createMethodExitRequest(); 322 323 entryRequest.addClassFilter(className); 324 exitRequest .addClassFilter(className); 325 326 entryRequest.enable(); 327 exitRequest .enable(); 328 329 } 330 } 331 else { // no classes were named, here comes the flood 332 MethodEntryRequest entryRequest; 333 MethodExitRequest exitRequest; 334 335 entryRequest = requestManager.createMethodEntryRequest(); 336 exitRequest = requestManager.createMethodExitRequest(); 337 338 entryRequest.enable(); 339 exitRequest .enable(); 340 } 341 } 342 343 // builds an official signature like 344 // com.company.package.ClassName.method(java.lang.String[], float) 345 // uses assembleArgs to make the argument list grabSignature(Method method)346 private String grabSignature(Method method) { 347 return method.declaringType().name() 348 + "." + method.name() + "(" 349 + assembleArgs(method) + ")"; 350 } 351 352 // gives a string which can be printed before the signature to 353 // show the current call sequence depth visually formIndentString()354 private String formIndentString() { 355 StringBuffer sb = new StringBuffer(); 356 for (int i = 0; i < indent; i++) { 357 sb.append(" "); 358 } 359 return sb.toString(); 360 } 361 362 // asks the method for its types, then assembles them for proper printing assembleArgs(Method method)363 private String assembleArgs(Method method) { 364 List argTypes = method.argumentTypeNames(); 365 Iterator iter = argTypes.iterator(); 366 StringBuffer sb = new StringBuffer(); 367 while (iter.hasNext()) { 368 sb.append(iter.next()); 369 if (iter.hasNext()) { 370 sb.append(", "); 371 } 372 } 373 return sb.toString(); 374 } 375 376 // the debugger must have a way to expell error message, lest it die 377 // from full buffers, this method arranges that dumpProcessOutput(Process proc)378 private void dumpProcessOutput(Process proc) { 379 dumpOutput(proc.getErrorStream()); 380 dumpOutput(proc.getInputStream()); 381 } 382 383 // spawns a thread so dumpStream can run concurrently with other threads dumpOutput(final InputStream stream)384 private void dumpOutput(final InputStream stream) { 385 Thread thread = new Thread() { 386 public void run() { 387 try { 388 dumpStream(stream); 389 } 390 catch (Exception e) { 391 System.err.println("dump failed for " + stream); 392 } 393 } 394 }; 395 thread.setPriority(Thread.MAX_PRIORITY - 1); 396 thread.start(); 397 } 398 399 // continually issues blocking reads stdin, or stderr from the virtual 400 // machines process 401 // prints the result to standard err. dumpStream(InputStream stream)402 private void dumpStream(InputStream stream) throws IOException { 403 BufferedReader in = new BufferedReader(new InputStreamReader(stream)); 404 String line; 405 while ((line = in.readLine()) != null) { 406 System.err.println(line); 407 } 408 } 409 printUsage()410 public static void printUsage() { 411 System.err.println("usage: java Seq methods_file output_file" 412 + " class [args...]"); 413 } 414 415 /** 416 * This is meant to be used, as shown in {@link #printUsage} above. 417 * @param args <br>method_file<br>output_file<br>class 418 * <br>[args_for_class...] 419 */ main(String[] args)420 public static void main(String[] args) throws Exception { 421 if (args.length < 3) { 422 printUsage(); 423 System.exit(1); 424 } 425 String[] passThroughArgs = new String[args.length - 2]; 426 for (int i = 2; i < args.length; i++) { 427 passThroughArgs[i - 2] = args[i]; 428 } 429 Seq s = new Seq(args[0], args[1], passThroughArgs); 430 } 431 } 432