001//- Copyright (C) 2014  James Riely, DePaul University
002//- Copyright (C) 2004  John Hamer, University of Auckland [Graphviz code]
003//-
004//-   This program is free software; you can redistribute it and/or
005//-   modify it under the terms of the GNU General Public License
006//-   as published by the Free Software Foundation; either version 2
007//-   of the License, or (at your option) any later version.
008//-
009//-   This program is distributed in the hope that it will be useful,
010//-   but WITHOUT ANY WARRANTY; without even the implied warranty of
011//-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012//-   GNU General Public License for more details.
013//-
014//-   You should have received a copy of the GNU General Public License along
015//-   with this program; if not, write to the Free Software Foundation, Inc.,
016//-   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.package algs;
017
018//- Event monitoring code based on
019//-    http://fivedots.coe.psu.ac.th/~ad/jg/javaArt5/
020//-    By Andrew Davison, ad@fivedots.coe.psu.ac.th, March 2009
021//-
022//- Graphviz code based on LJV
023//-    https://www.cs.auckland.ac.nz/~j-hamer/
024//-    By John Hamer, <J.Hamer@cs.auckland.ac.nz>, 2003
025//-    Copyright (C) 2004  John Hamer, University of Auckland
026
027package stdlib;
028
029import java.io.*;
030import java.util.ArrayList;
031import java.util.HashMap;
032import java.util.HashSet;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.Locale;
037import java.util.Map;
038import java.util.NoSuchElementException;
039import java.util.Set;
040import java.util.TreeMap;
041import java.util.function.Consumer;
042import com.sun.jdi.*;
043import com.sun.jdi.connect.*;
044import com.sun.jdi.connect.Connector.Argument;
045import com.sun.jdi.event.*;
046import com.sun.jdi.request.*;
047
048/**
049 * <p>
050 * Traces the execution of a target program.
051 * </p><p>
052 * See <a href="http://fpl.cs.depaul.edu/jriely/visualization/">http://fpl.cs.depaul.edu/jriely/visualization/</a>
053 * </p><p>
054 * Command-line usage: java Trace [OptionalJvmArguments] fullyQualifiedClassName
055 * </p><p>
056 * Starts a new JVM (java virtual machine) running the main program in
057 * fullyQualifiedClassName, then traces it's behavior. The OptionalJvmArguments
058 * are passed to this underlying JVM.
059 * </p><p>
060 * Example usages:
061 * </p><pre>
062 *   java Trace MyClass
063 *   java Trace mypackage.MyClass
064 *   java Trace -cp ".:/pathTo/Library.jar" mypackage.MyClass  // mac/linux
065 *   java Trace -cp ".;/pathTo/Library.jar" mypackage.MyClass  // windows
066 * </pre><p>
067 * Two types of display are support: console and graphviz In order to use
068 * graphziv, you must install http://www.graphviz.org/ and perhaps call
069 * graphvizAddPossibleDotLocation to include the location of the "dot"
070 * executable.
071 * </p><p>
072 * You can either draw the state at each step --- {@code drawSteps()} --- or
073 * you can draw states selectively by calling {@code Trace.draw()} from the program
074 * you are tracing. See the example in {@code ZTraceExample.java}.
075 * </p><p>
076 * Classnames that end in "Node" are drawn using rounded rectangles by default.  
077 * This does not work until the class is loaded; thus, null references may show up 
078 * as inline fields for a while, until the class is loaded.
079 * </p>
080 * @author James Riely, jriely@cs.depaul.edu, 2014-2015
081 */
082/* !!!!!!!!!!!!!!!!!!!!!!!! COMPILATION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
083 *
084 * This class requires Java 8. It also requires the file tools.jar, which comes
085 * with the JDK (not the JRE)
086 *
087 * On windows, you can find it in
088 *
089 * <pre>
090 *   C:\Program Files\Java\jdk...\lib\tools.jar
091 * </pre>
092 *
093 * On mac, look in
094 *
095 * <pre>
096 *   /Library/Java/JavaVirtualMachines/jdk.../Contents/Home/lib/tools.jar
097 * </pre>
098 *
099 * To put this in your eclipse build path, select your project in the package
100 * explorer, then:
101 *
102 * <pre>
103 *  Project > Properties > Java Build Path > Libraries > Add External Library
104 * </pre>
105 *
106 */
107public class Trace {
108        private Trace () {} // noninstantiable class
109        protected static final String CALLBACK_CLASS_NAME = Trace.class.getCanonicalName ();
110        protected static final String GRAPHVIZ_CLASS_NAME = Graphviz.class.getCanonicalName ();
111
112        /**
113         * Draw the given object.
114         *
115         * This is a stub method, which is trapped by the debugger. It only has an
116         * effect if a variant of Trace.run() has been called to start the debugger
117         * and Trace.drawSteps() is false.
118         */     
119        public static void drawObject (Object object) { } // See Printer. methodEntryEvent      
120        protected static final String CALLBACK_DRAW_OBJECT = "drawObject";
121        protected static final HashSet<String> CALLBACKS = new HashSet<> ();
122        static { CALLBACKS.add (CALLBACK_DRAW_OBJECT); }
123        /**
124         * Draw the given object, labeling it with the given name.
125         *
126         * This is a stub method, which is trapped by the debugger. It only has an
127         * effect if a variant of Trace.run() has been called to start the debugger
128         * and Trace.drawSteps() is false.
129         */
130        public static void drawObjectWithName (String name, Object object) { } // See Printer. methodEntryEvent
131        protected static final String CALLBACK_DRAW_OBJECT_NAMED = "drawObjectWithName";
132        static { CALLBACKS.add (CALLBACK_DRAW_OBJECT_NAMED); }
133        /**
134         * Draw the given objects.
135         *
136         * This is a stub method, which is trapped by the debugger. It only has an
137         * effect if a variant of Trace.run() has been called to start the debugger
138         * and Trace.drawSteps() is false.
139         */
140        public static void drawObjects (Object... objects) { } // See Printer. methodEntryEvent
141        protected static final String CALLBACK_DRAW_OBJECTS = "drawObjects";
142        static { CALLBACKS.add (CALLBACK_DRAW_OBJECTS); }
143        /**
144         * Draw the given objects, labeling them with the given names. The array of
145         * namesAndObjects must alternate between names and objects, as in
146         * Trace.drawObjects("x", x, "y" y).
147         *
148         * This is a stub method, which is trapped by the debugger. It only has an
149         * effect if a variant of Trace.run() has been called to start the debugger
150         * and Trace.drawSteps() is false.
151         */
152        public static void drawObjectsWithNames (Object... namesAndObjects) { }  // See Printer. methodEntryEvent
153        protected static final String CALLBACK_DRAW_OBJECTS_NAMED = "drawObjectsWithNames";
154        static { CALLBACKS.add (CALLBACK_DRAW_OBJECTS_NAMED); }
155
156        /**
157         * Draw the current frame, as well as all reachable objects.
158         *
159         * This is a stub method, which is trapped by the debugger. It only has an
160         * effect if a variant of Trace.run() has been called to start the debugger
161         * and Trace.drawSteps() is false.
162         */
163        public static void drawThisFrame () { }  // See Printer. methodEntryEvent
164        protected static final String CALLBACK_DRAW_THIS_FRAME = "drawThisFrame";
165        static { CALLBACKS.add (CALLBACK_DRAW_THIS_FRAME); }
166
167        /**
168         * Stop drawing steps.
169         *
170         * This is a stub method, which is trapped by the debugger. It only has an
171         * effect if a variant of Trace.run() has been called to start the debugger.
172         */
173        public static void drawStepsEnd () { }  // See Printer. methodEntryEvent
174        protected static final String CALLBACK_DRAW_STEPS_END = "drawStepsEnd";
175        static { CALLBACKS.add (CALLBACK_DRAW_STEPS_END); }
176
177        /**
178         * Start drawing steps.
179         *
180         * This is a stub method, which is trapped by the debugger. It only has an
181         * effect if a variant of Trace.run() has been called to start the debugger.
182         */
183        public static void drawSteps () { }  // See Printer. methodEntryEvent
184        protected static final String CALLBACK_DRAW_STEPS_BEGIN = "drawSteps";
185        static { CALLBACKS.add (CALLBACK_DRAW_STEPS_BEGIN); }
186
187        /**
188         * Draw all stack frames and static variables, as well as all reachable
189         * objects.
190         *
191         * This is a stub method, which is trapped by the debugger. It only has an
192         * effect if a variant of Trace.run() has been called to start the debugger
193         * and Trace.drawSteps() is false.
194         */
195        public static void draw () { }  // See Printer. methodEntryEvent
196        protected static final String CALLBACK_DRAW_ALL_FRAMES = "draw";
197        static { CALLBACKS.add (CALLBACK_DRAW_ALL_FRAMES); }
198
199        /**
200         * Draw all stack frames and static variables, as well as all reachable
201         * objects, overriding showStaticClasses() if it is false.
202         *
203         * This is a stub method, which is trapped by the debugger. It only has an
204         * effect if a variant of Trace.run() has been called to start the debugger
205         * and Trace.drawSteps() is false.
206         */
207        public static void drawAll () { }  // See Printer. methodEntryEvent
208        protected static final String CALLBACK_DRAW_ALL_FRAMES_AND_STATICS = "drawAll";
209        static { CALLBACKS.add (CALLBACK_DRAW_ALL_FRAMES_AND_STATICS); }
210
211        /**
212         * Clear the call tree, removing all previous entries.
213         *
214         * This is a stub method, which is trapped by the debugger. It only has an
215         * effect if a variant of Trace.run() has been called to start the debugger.
216         */
217        public static void clearCallTree () { }  // See Printer. methodEntryEvent
218        protected static final String CALLBACK_CLEAR_CALL_TREE = "clearCallTree";
219
220        // Basic graphviz options
221        /**
222         * Run graphviz "dot" program to produce an output file (default==true). If
223         * false, a graphviz source file is created, but no graphic file is
224         * generated.
225         */
226        public static void graphvizRunGraphviz (boolean value) {
227                GRAPHVIZ_RUN_GRAPHVIZ = value;
228        }
229        protected static boolean GRAPHVIZ_RUN_GRAPHVIZ = true;
230
231        /**
232         * The graphviz format -- see http://www.graphviz.org/doc/info/output.html .
233         * (default=="png").
234         */
235        public static void graphvizOutputFormat (String value) {
236                GRAPHVIZ_OUTPUT_FORMAT = value;
237        }
238        protected static String GRAPHVIZ_OUTPUT_FORMAT = "png";
239
240        /**
241         * Sets the graphviz output directory.
242         * Creates the directory if necessary.
243         * Relative pathnames are interpreted with respect to the user's "Desktop" directory.
244         * Default is "GraphvizOutput".
245         */
246        public static void setGraphizOutputDir (String dirName) {
247                GRAPHVIZ_DIR = dirName;
248        }
249        private static String GRAPHVIZ_DIR = "GraphvizOutput";
250
251        /**
252         * Sets the console output to the given filename. Console output will be
253         * written to:
254         *
255         * <pre>
256         * (user home directory)/(filename)
257         * </pre>
258         */
259        public static void setConsoleFilenameRelativeToUserDesktop (String filename) {
260                setConsoleFilename (Graphviz.getDesktop() + File.separator + filename);
261        }
262        /**
263         * Sets the console output to the given filename.
264         */
265        public static void setConsoleFilename (String filename) {
266                Printer.setFilename (filename);
267        }
268        /**
269         * Sets the console output to the default (the terminal).
270         */
271        public static void setConsoleFilename () {
272                Printer.setFilename ();
273        }
274
275        /**
276         * Run graphviz "dot" program to produce an output file (default==true). If
277         * false, a graphviz source file is created, but no graphic file is
278         * generated.
279         */
280        public static void showOnlyTopFrame (boolean value) {
281                GRAPHVIZ_SHOW_ONLY_TOP_FRAME = value;
282        }
283        protected static boolean GRAPHVIZ_SHOW_ONLY_TOP_FRAME = false;
284
285        // Basic options --
286        protected static boolean GRAPHVIZ_SHOW_STEPS = false;
287        //      protected static void drawStepsOf (String className, String methodName) {
288        //              GRAPHVIZ_SHOW_STEPS = true;
289        //              if (GRAPHVIZ_SHOW_STEPS_OF == null)
290        //                      GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>();
291        //              GRAPHVIZ_SHOW_STEPS_OF.add (new OptionalClassNameWithRequiredMethodName (className, methodName));
292        //              REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = (GRAPHVIZ_SHOW_STEPS_OF.size () <= 1);
293        //      }
294        /**
295         * Create a new graphviz drawing for every step of the named method.
296         * The methodName does not include parameters.
297         * In order to show a constructor, use the method name {@code <init>}.
298         */
299        public static void drawStepsOfMethod (String methodName) { }  // See Printer. methodEntryEvent
300        /**
301         * Create a new graphviz drawing for every step of the named methods.
302         * The methodName does not include parameters.
303         * In order to show a constructor, use the method name {@code <init>}.
304         */
305        public static void drawStepsOfMethods (String... methodName) { }  // See Printer. methodEntryEvent
306        protected static final String CALLBACK_DRAW_STEPS_OF_METHOD = "drawStepsOfMethod";
307        static { CALLBACKS.add (CALLBACK_DRAW_STEPS_OF_METHOD); }
308        protected static final String CALLBACK_DRAW_STEPS_OF_METHODS = "drawStepsOfMethods";
309        static { CALLBACKS.add (CALLBACK_DRAW_STEPS_OF_METHODS); }
310        protected static void drawStepsOfMethodBegin (String methodName) {
311                GRAPHVIZ_SHOW_STEPS = true;
312                if (GRAPHVIZ_SHOW_STEPS_OF == null)
313                        GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>();
314                GRAPHVIZ_SHOW_STEPS_OF.add (new OptionalClassNameWithRequiredMethodName (null, methodName));
315                REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = (GRAPHVIZ_SHOW_STEPS_OF.size () <= 1);
316        }
317        protected static void drawStepsOfMethodEnd () {
318                GRAPHVIZ_SHOW_STEPS = true;
319                GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>();
320                REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = false;
321        }
322
323        protected static boolean drawStepsOfInternal (ThreadReference thr) {
324                if (GRAPHVIZ_SHOW_STEPS_OF == null) return true;
325                List<StackFrame> frames = null;
326                try { frames = thr.frames (); } catch (IncompatibleThreadStateException e) { }
327                return Trace.drawStepsOfInternal (frames, null);
328        }
329        protected static boolean drawStepsOfInternal (List<StackFrame> frames, Value returnVal) {
330                if (GRAPHVIZ_SHOW_STEPS_OF == null) return true;
331                if (frames == null) return true;
332                if (REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF && returnVal != null && !(returnVal instanceof VoidValue)) return false;
333                StackFrame currentFrame = frames.get (0);
334                String className = currentFrame.location ().declaringType ().name ();
335                String methodName = currentFrame.location ().method ().name ();
336                return Trace.drawStepsOfInternal (className, methodName);
337        }
338        protected static boolean drawStepsOfInternal (String className, String methodName) {
339                if (GRAPHVIZ_SHOW_STEPS_OF == null) return true;
340                //System.err.println (className + "." + methodName + " " + new OptionalClassNameWithRequiredMethodName(className, methodName).hashCode() + " "+ GRAPHVIZ_SHOW_STEPS_OF);
341                return GRAPHVIZ_SHOW_STEPS_OF.contains (new OptionalClassNameWithRequiredMethodName(className, methodName));
342        }
343        protected static Set<OptionalClassNameWithRequiredMethodName> GRAPHVIZ_SHOW_STEPS_OF = null;
344        private static boolean REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = false;
345        protected static class OptionalClassNameWithRequiredMethodName {
346                String className;
347                String methodName;
348                public OptionalClassNameWithRequiredMethodName (String className, String methodName) {
349                        this.className = className;
350                        this.methodName = methodName;
351                }
352                public String toString () {
353                        return className + "." + methodName;
354                }
355                public boolean equals (Object other) {
356                        //System.err.println (this + "==" + other);
357                        if (other == this) return true;
358                        if (other == null) return false;
359                        if (other.getClass () != this.getClass ()) return false;
360                        OptionalClassNameWithRequiredMethodName that = (OptionalClassNameWithRequiredMethodName) other;
361                        if (this.className != null && that.className != null) {
362                                if (! this.className.equals (that.className)) return false;
363                        }
364                        if (! this.methodName.equals (that.methodName)) return false;
365                        return true;
366                }
367                public int hashCode() { return methodName.hashCode (); }
368        }
369        /**
370         * Show events on the console (default==false).
371         */
372        public static void consoleShow (boolean value) {
373                CONSOLE_SHOW_THREADS = value;
374                CONSOLE_SHOW_CLASSES = value;
375                CONSOLE_SHOW_CALLS = value;
376                CONSOLE_SHOW_STEPS = value;
377                CONSOLE_SHOW_VARIABLES = value;
378                CONSOLE_SHOW_STEPS_VERBOSE = false;
379        }
380        /**
381         * Show events on the console, including code (default==false).
382         */
383        public static void consoleShowVerbose (boolean value) {
384                CONSOLE_SHOW_THREADS = value;
385                CONSOLE_SHOW_CLASSES = value;
386                CONSOLE_SHOW_CALLS = value;
387                CONSOLE_SHOW_STEPS = value;
388                CONSOLE_SHOW_VARIABLES = value;
389                CONSOLE_SHOW_STEPS_VERBOSE = true;
390        }
391
392        /**
393         * Directory for source code. This needs to be fixed before the class loading
394         * begins. So currently no way to change dynamically.
395         */
396        protected static String SOURCE_DIRECTORY = "src";
397        protected static boolean CONSOLE_SHOW_THREADS = false;
398        protected static boolean CONSOLE_SHOW_CLASSES = false;
399        protected static boolean CONSOLE_SHOW_CALLS = false;
400        protected static boolean CONSOLE_SHOW_STEPS = false;
401        protected static boolean CONSOLE_SHOW_STEPS_VERBOSE = false;
402        protected static boolean CONSOLE_SHOW_VARIABLES = false;
403        /**
404         * Show String, Integer, Double, etc as simplified objects (default==false).
405         */
406        public static void showBuiltInObjects (boolean value) {
407                if (value) showNodesAsRegularObjects ();
408                SHOW_STRINGS_AS_PRIMITIVE = !value;
409                SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = !value;
410                GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = true;
411        }
412        /**
413         * Show String, Integer, Double, etc as regular objects (default==false).
414         */
415        public static void showBuiltInObjectsVerbose (boolean value) {
416                if (value) showNodesAsRegularObjects ();
417                SHOW_STRINGS_AS_PRIMITIVE = !value;
418                SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = !value;
419                GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = false;
420        }
421        protected static boolean SHOW_STRINGS_AS_PRIMITIVE = true;
422        protected static boolean SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = true;
423        protected static boolean GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = true;
424
425        /**
426         * Run the debugger on the current class. Execution of the current program
427         * ends and a new JVM is started under the debugger.
428         *
429         * The debugger will execute the main method of the class that calls run.
430         *
431         * The main method is run with no arguments.
432         */
433        public static void run () {
434                Trace.run (new String[] {});
435        }
436        /**
437         * Run the debugger on the current class. Execution of the current program
438         * ends and a new JVM is started under the debugger.
439         *
440         * The debugger will execute the main method of the class that calls run.
441         *
442         * The main method is run with the given arguments.
443         */
444        public static void run (String[] args) {
445                StackTraceElement[] stackTrace = Thread.currentThread ().getStackTrace ();
446                String mainClassName = stackTrace[stackTrace.length - 1].getClassName ();
447                Trace.internalPrepAndRun (mainClassName, args, true);
448        }
449        /**
450         * Run the debugger on the given class. The current program will continue to
451         * execute after this call to run has completed.
452         *
453         * The main method of the given class is called with no arguments.
454         */
455        public static void run (String mainClassName) {
456                Trace.internalPrepAndRun (mainClassName, new String[] {}, false);
457        }
458        /**
459         * Run the debugger on the given class. The current program will continue to
460         * execute after this call to run has completed.
461         *
462         * The main method of the given class is called with no arguments.
463         */
464        public static void run (Class<?> mainClass) {
465                Trace.run (mainClass, new String[] {});
466        }
467        /**
468         * Run the debugger on the given class. The current program will continue to
469         * execute after this call to run has completed.
470         *
471         * The main method of the given class is called with the given arguments.
472         */
473        public static void run (String mainClassName, String[] args) {
474                internalPrepAndRun (mainClassName, args, false);
475        }
476        /**
477         * Run the debugger on the given class. The current program will continue to
478         * execute after this call to run has completed.
479         *
480         * The main method of the given class is called with the given arguments.
481         */
482        public static void run (Class<?> mainClass, String[] args) {
483                Trace.internalPrepAndRun (mainClass.getCanonicalName (), args, false);
484        }
485        /**
486         * Run the debugger on the given class. The current program will continue to
487         * execute after this call to run has completed.
488         *
489         * The main method of the given class is called with the given arguments.
490         */
491        public static void runWithArgs (Class<?> mainClass, String... args) {
492                Trace.run (mainClass, args);
493        }
494        /**
495         * Run the debugger on the given class. The current program will continue to
496         * execute after this call to run has completed.
497         *
498         * The main method of the given class is called with the given arguments.
499         */
500        public static void runWithArgs (String mainClassName, String... args) {
501                Trace.internalPrepAndRun (mainClassName, args, false);
502        }
503        /**
504         * The debugger can be invoked from the command line using this method. The
505         * first argument must be the fully qualified name of the class to be
506         * debugged. Addition arguments are passed to the main method of the class
507         * to be debugged.
508         */
509        public static void main (String[] args) {
510                if (args.length == 0) {
511                        System.err.println ("Usage: java " + Trace.class.getCanonicalName () + " [OptionalJvmArguments] fullyQualifiedClassName");
512                        System.exit (-1);
513                }
514                int length = PREFIX_ARGS_FOR_VM.size ();
515                String[] allArgs = new String[length + args.length];
516                for (int i = 0; i < length; i++)
517                        allArgs[i] = PREFIX_ARGS_FOR_VM.get (i);
518                System.arraycopy (args, 0, allArgs, length, args.length);
519                internalRun ("Trace", allArgs, false);
520        }
521
522        //------------------------------------------------------------------------
523        //          _______.___________.  ______   .______      __     __
524        //         /       |           | /  __  \  |   _  \    |  |   |  |
525        //        |   (----`---|  |----`|  |  |  | |  |_)  |   |  |   |  |
526        //         \   \       |  |     |  |  |  | |   ___/    |  |   |  |
527        //     .----)   |      |  |     |  `--'  | |  |        |__|   |__|
528        //     |_______/       |__|      \______/  | _|        (__)   (__)
529        //
530        //          _______.  ______     ___      .______     ____    ____
531        //         /       | /      |   /   \     |   _  \    \   \  /   /
532        //        |   (----`|  ,----'  /  ^  \    |  |_)  |    \   \/   /
533        //         \   \    |  |      /  /_\  \   |      /      \_    _/
534        //     .----)   |   |  `----./  _____  \  |  |\  \----.   |  |
535        //     |_______/     \______/__/     \__\ | _| `._____|   |__|
536        //
537        //     .___________. __    __   __  .__   __.   _______      _______.
538        //     |           ||  |  |  | |  | |  \ |  |  /  _____|    /       |
539        //     `---|  |----`|  |__|  | |  | |   \|  | |  |  __     |   (----`
540        //         |  |     |   __   | |  | |  . `  | |  | |_ |     \   \
541        //         |  |     |  |  |  | |  | |  |\   | |  |__| | .----)   |
542        //         |__|     |__|  |__| |__| |__| \__|  \______| |_______/
543        //
544        //     .______    _______  __        ______   ____    __    ____    __
545        //     |   _  \  |   ____||  |      /  __  \  \   \  /  \  /   /   |  |
546        //     |  |_)  | |  |__   |  |     |  |  |  |  \   \/    \/   /    |  |
547        //     |   _  <  |   __|  |  |     |  |  |  |   \            /     |  |
548        //     |  |_)  | |  |____ |  `----.|  `--'  |    \    /\    /      |__|
549        //     |______/  |_______||_______| \______/      \__/  \__/       (__)
550        //
551        //------------------------------------------------------------------------
552        // This program uses all sorts of crazy java foo.
553        // You should not have to read anything else in this file.
554
555        /**
556         * This code is based on the Trace.java example included in the
557         * demo/jpda/examples.jar file in the JDK.
558         *
559         * For more information on JPDA and JDI, see:
560         *
561         * <pre>
562         * http://docs.oracle.com/javase/8/docs/technotes/guides/jpda/trace.html
563         * http://docs.oracle.com/javase/8/docs/jdk/api/jpda/jdi/index.html
564         * http://forums.sun.com/forum.jspa?forumID=543
565         * </pre>
566         *
567         * Changes made by Riely:
568         *
569         * - works with packages other than default
570         *
571         * - prints values of variables Objects values are printed as "@uniqueId"
572         * Arrays include the values in the array, up to
573         *
574         * - handles exceptions
575         *
576         * - works for arrays, when referenced by local variables, static fields, or
577         * fields of "this"
578         *
579         * - options for more or less detail
580         *
581         * - indenting to show position in call stack
582         *
583         * - added methods to draw the state of the system using graphviz
584         *
585         * Known bugs/limitations:
586         *
587         * - There appears to be a bug in the JDI: steps from static initializers
588         * are not always reported. I don't see a real pattern here. Some static
589         * initializers work and some don't. When the step event is not generated by
590         * the JDI, this code cannot report on it, of course, since we rely on the
591         * JDI to generate the steps.
592         *
593         * - Works for local arrays, including updates to fields of "this", but will
594         * not print changes through other object references, such as
595         * yourObject.publicArray[0] = 22 As long as array fields are private (as
596         * they should be), it should be okay.
597         *
598         * - Updates to arrays that are held both in static fields and also in local
599         * variables or object fields will be shown more than once in the console
600         * view.
601         *
602         * - Space leak: copies of array references are kept forever. See
603         * "registerArray".
604         *
605         * - Not debugged for multithreaded code. Monitor events are currently
606         * ignored.
607         *
608         * - Slow. Only good for short programs.
609         *
610         * - This is a hodgepodge of code from various sources, not well debugged,
611         * not super clean.
612         *
613         */
614        /**
615         * Macintosh OS-X sometimes sets the hostname to an unroutable name and this
616         * may cause the socket connection to fail. To see your hostname, open a
617         * Terminal window and type the "hostname" command. On my machine, the
618         * terminal prompt is "$", and so the result looks like this:
619         *
620         * <pre>
621         *   $ hostname
622         *   escarole.local
623         *   $
624         * </pre>
625         *
626         * To see that this machine is routable, I can "ping" it:
627         *
628         * <pre>
629         *   $ ping escarole.local
630         *   PING escarole.local (192.168.1.109): 56 data bytes
631         *   64 bytes from 192.168.1.109: icmp_seq=0 ttl=64 time=0.046 ms
632         *   64 bytes from 192.168.1.109: icmp_seq=1 ttl=64 time=0.104 ms
633         *   ^C
634         *   --- escarole.local ping statistics ---
635         *   2 packets transmitted, 2 packets received, 0.0% packet loss
636         *   round-trip min/avg/max/stddev = 0.046/0.075/0.104/0.029 ms
637         * </pre>
638         *
639         * When I am connected to some networks, the result is like this:
640         *
641         * <pre>
642         *   $ hostname
643         *   loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu
644         *   $ ping loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu
645         *   ping: cannot resolve loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu: Unknown host
646         * </pre>
647         *
648         * Or this:
649         *
650         * <pre>
651         *   $ hostname
652         *   asteelembook.cstcis.cti.depaul.edu
653         *   $ ping asteelembook.cstcis.cti.depaul.edu
654         *   PING asteelembook.cstcis.cti.depaul.edu (140.192.38.100): 56 data bytes
655         *   Request timeout for icmp_seq 0
656         *   Request timeout for icmp_seq 1
657         *   ^C
658         *   --- asteelembook.cstcis.cti.depaul.edu ping statistics ---
659         *   3 packets transmitted, 0 packets received, 100.0% packet loss
660         * </pre>
661         *
662         * To stop OS-X from taking bogus hostname like this, you can fix the
663         * hostname, as follows:
664         *
665         * <pre>
666         *   $ scutil --set HostName escarole.local
667         *   $
668         * </pre>
669         *
670         * Where "escarole" is your computer name (no spaces or punctuation). You
671         * will be prompted for your password in order to modify the configuration.
672         *
673         * To reset OS-X to it's default behavior, do this:
674         *
675         * <pre>
676         *   $ scutil --set HostName ""
677         *   $
678         * </pre>
679         *
680         * On OSX 10.10 (Yosemite), apple seems to have turned of DNS lookup for
681         * .local addresses.
682         *
683         * https://discussions.apple.com/thread/6611817?start=13
684         *
685         * To fix this, you need to
686         *
687         * <pre>
688         * sudo vi /etc/hosts
689         * </pre>
690         *
691         * and change the line
692         *
693         * <pre>
694         *   127.0.0.1       localhost
695         * </pre>
696         *
697         * to
698         *
699         * <pre>
700         *   127.0.0.1       localhost escarole.local
701         * </pre>
702         *
703         * More robustly, the following "patch" fixes the JDI so that it uses the ip
704         * address, rather than the hostname. The code is called from
705         *
706         * <pre>
707         * com.sun.tools.jdi.SunCommandLineLauncher.launch ()
708         * </pre>
709         *
710         * which calls
711         *
712         * <pre>
713         * com.sun.tools.jdi.SocketTransportService.SocketListenKey.address ()
714         * </pre>
715         *
716         * Here is the patch. Just compile this and put in your classpath before
717         * tools.jar.
718         *
719         * <pre>
720         * package com.sun.tools.jdi;
721         *
722         * import java.net.*;
723         *
724         * class SocketTransportService$SocketListenKey extends com.sun.jdi.connect.spi.TransportService.ListenKey {
725         *     ServerSocket ss;
726         *     SocketTransportService$SocketListenKey (ServerSocket ss) {
727         *         this.ss = ss;
728         *     }
729         *     ServerSocket socket () {
730         *         return ss;
731         *     }
732         *     public String toString () {
733         *         return address ();
734         *     }
735         *
736         *     // Returns the string representation of the address that this listen key represents.
737         *     public String address () {
738         *         InetAddress address = ss.getInetAddress ();
739         *
740         *         // If bound to the wildcard address then use current local hostname. In
741         *         // the event that we don't know our own hostname then assume that host
742         *         // supports IPv4 and return something to represent the loopback address.
743         *         if (address.isAnyLocalAddress ()) {
744         *             // JWR: Only change is to comment out the lines below
745         *             // try {
746         *             //     address = InetAddress.getLocalHost ();
747         *             // } catch (UnknownHostException uhe) {
748         *             byte[] loopback = { 0x7f, 0x00, 0x00, 0x01 };
749         *             try {
750         *                 address = InetAddress.getByAddress (&quot;127.0.0.1&quot;, loopback);
751         *             } catch (UnknownHostException x) {
752         *                 throw new InternalError (&quot;unable to get local hostname&quot;);
753         *             }
754         *             //  }
755         *         }
756         *
757         *         // Now decide if we return a hostname or IP address. Where possible
758         *         // return a hostname but in the case that we are bound to an address
759         *         // that isn't registered in the name service then we return an address.
760         *         String result;
761         *         String hostname = address.getHostName ();
762         *         String hostaddr = address.getHostAddress ();
763         *         if (hostname.equals (hostaddr)) {
764         *             if (address instanceof Inet6Address) {
765         *                 result = &quot;[&quot; + hostaddr + &quot;]&quot;;
766         *             } else {
767         *                 result = hostaddr;
768         *             }
769         *         } else {
770         *             result = hostname;
771         *         }
772         *
773         *         // Finally return &quot;hostname:port&quot;, &quot;ipv4-address:port&quot; or &quot;[ipv6-address]:port&quot;.
774         *         return result + &quot;:&quot; + ss.getLocalPort ();
775         *     }
776         * }
777         * </pre>
778         */
779        private static String IN_DEBUGGER = "TraceDebuggingVMHasLaunched";
780        private static boolean insideTestVM () {
781                return System.getProperty (IN_DEBUGGER) != null;
782        }
783        /**
784         * Prepares the args and then calls internalRun.
785         */
786        private static void internalPrepAndRun (String mainClassName, String[] args, boolean terminateAfter) {          
787                int length = PREFIX_ARGS_FOR_VM.size ();
788                String[] allArgs = new String[length + args.length];
789                for (int i = 0; i < length; i++)
790                        allArgs[i] = PREFIX_ARGS_FOR_VM.get (i);
791                System.arraycopy (args, 0, allArgs, length, args.length);
792//              String[] allArgs = new String[length + args.length + 1];
793//              for (int i = 0; i < length; i++)
794//                      allArgs[i] = PREFIX_ARGS_FOR_VM.get (i);
795//              allArgs[length] = mainClassName;
796//              System.arraycopy (args, 0, allArgs, length + 1, args.length);
797                internalRun (mainClassName, allArgs, terminateAfter);
798        }
799        /**
800         * This is the function that starts the JVM. If terminateAfter is true, then
801         * the current thread is killed after the debug JVM terminates.
802         */
803        @SuppressWarnings("deprecation")
804        private static void internalRun (String mainClassName, String[] allArgs, boolean terminateAfter) {
805                if (insideTestVM ()) return;
806                Graphviz.setOutputDirectory (GRAPHVIZ_DIR, mainClassName);
807                VirtualMachine vm = launchConnect (mainClassName, allArgs);
808                monitorJVM (vm);
809                if (terminateAfter) Thread.currentThread ().stop ();
810        }
811
812        /**
813         * Prefix options for the debugger VM. By default, the classpath is set to
814         * the current classpath. Other options can be provided here.
815         */
816        public static void addPrefixOptionsForVm (String value) {
817                PREFIX_ARGS_FOR_VM.add (value);
818        }
819        protected static ArrayList<String> PREFIX_ARGS_FOR_VM;
820        static {
821                String BIN_PATH = System.getProperty ("user.dir") + System.getProperty ("file.separator") + "bin";
822                PREFIX_ARGS_FOR_VM = new ArrayList<> ();
823                PREFIX_ARGS_FOR_VM.add ("-cp");
824                PREFIX_ARGS_FOR_VM.add ("\"" 
825                                + System.getProperty ("java.class.path") 
826                                + System.getProperty ("path.separator")
827                                + BIN_PATH
828                                + System.getProperty ("path.separator")
829                                + ".\""
830                                ); 
831                PREFIX_ARGS_FOR_VM.add ("-D" + IN_DEBUGGER + "=true");
832                //for (Object key : System.getProperties().keySet()) {
833                //      System.out.printf("%s=%s\n", key, System.getProperty((String)key));
834                //}
835                //System.out.printf("java.home=%s\n", System.getProperty("java.home"));
836                //System.out.printf("java.class.path=%s\n", System.getProperty("java.class.path"));
837        }
838        /**
839         * Turn on debugging information (default==false). Intended for developers.
840         */
841        public static void debug (boolean value) {
842                DEBUG = value;
843        }
844        protected static boolean DEBUG = false;
845
846        /**
847         * Add an exclude pattern. Classes whose fully qualified name matches an
848         * exclude pattern are ignored by the debugger. Regular expressions are
849         * limited to exact matches and patterns that begin with '*' or end with
850         * '*'; for example, "*.Foo" or "java.*". This limitation is inherited from
851         * {@code com.sun.jdi.request.WatchpointRequest}. The default exclude
852         * patterns include:
853         *
854         * <pre>
855         *  "*$$Lambda$*" "java.*" "jdk.*" "sun.*"  "com.*"  "org.*"  "javax.*"  "apple.*"  "Jama.*"  "qs.*"
856         *  "stdlib.A*" "stdlib.B*" "stdlib.C*" "stdlib.D*" "stdlib.E*" "stdlib.F*" "stdlib.G*" "stdlib.H*" 
857         *  "stdlib.I*" "stdlib.J*" "stdlib.K*" "stdlib.L*" "stdlib.M*" "stdlib.N*" "stdlib.O*" "stdlib.P*" 
858         *  "stdlib.Q*" "stdlib.R*" "stdlib.S*" 
859         *  "stdlib.U*" "stdlib.V*" "stdlib.W*" "stdlib.X*" "stdlib.Y*" 
860         * </pre>
861         * 
862         * The JDI excludes classes, but does not allow exceptions. This is
863         * the reason for the unusual number of excludes for {@code
864         * stdlib}. It is important that {@code stdlib.Trace} not be excluded
865         * --- if it were, then callBacks to {@code Trace.draw} would
866         * not function.  As a result, all classes in {@code stdlib}
867         * that start with a letter other than {@code T} are excluded.
868         * Be careful when adding classes to {@code stdlib}.
869         *
870         * Exclude patterns must include
871         * 
872         * <pre>
873         *  "*$$Lambda$*" "java.*" "jdk.*" "sun.*"
874         * </pre>
875         * 
876         * otherwise the Trace code itself will fail to run.
877         */
878        public static void addExcludePattern (String value) {
879                EXCLUDE_GLOBS.add (value);
880        }
881        /**
882         * Remove an exclude pattern.
883         *
884         * @see addExcludePattern
885         */
886        public static void removeExcludePattern (String value) {
887                EXCLUDE_GLOBS.remove (value);
888        }
889        protected static HashSet<String> EXCLUDE_GLOBS;
890        static {
891                EXCLUDE_GLOBS = new HashSet<> ();
892                EXCLUDE_GLOBS.add ("*$$Lambda$*");
893                EXCLUDE_GLOBS.add ("java.*");
894                EXCLUDE_GLOBS.add ("jdk.*");
895                EXCLUDE_GLOBS.add ("sun.*");
896                EXCLUDE_GLOBS.add ("com.*");
897                EXCLUDE_GLOBS.add ("org.*");
898                EXCLUDE_GLOBS.add ("javax.*");
899                EXCLUDE_GLOBS.add ("apple.*");
900                EXCLUDE_GLOBS.add ("Jama.*");
901                EXCLUDE_GLOBS.add ("qs.*");
902                EXCLUDE_GLOBS.add ("stdlib.A*");
903                EXCLUDE_GLOBS.add ("stdlib.B*");
904                EXCLUDE_GLOBS.add ("stdlib.C*");
905                EXCLUDE_GLOBS.add ("stdlib.D*");
906                EXCLUDE_GLOBS.add ("stdlib.E*");
907                EXCLUDE_GLOBS.add ("stdlib.F*");
908                EXCLUDE_GLOBS.add ("stdlib.G*");
909                EXCLUDE_GLOBS.add ("stdlib.H*");
910                EXCLUDE_GLOBS.add ("stdlib.I*");
911                EXCLUDE_GLOBS.add ("stdlib.J*");
912                EXCLUDE_GLOBS.add ("stdlib.K*");
913                EXCLUDE_GLOBS.add ("stdlib.L*");
914                EXCLUDE_GLOBS.add ("stdlib.M*");
915                EXCLUDE_GLOBS.add ("stdlib.N*");
916                EXCLUDE_GLOBS.add ("stdlib.O*");
917                EXCLUDE_GLOBS.add ("stdlib.P*");
918                EXCLUDE_GLOBS.add ("stdlib.Q*");
919                EXCLUDE_GLOBS.add ("stdlib.R*");
920                EXCLUDE_GLOBS.add ("stdlib.S*");
921                EXCLUDE_GLOBS.add ("stdlib.U*");
922                EXCLUDE_GLOBS.add ("stdlib.V*");
923                EXCLUDE_GLOBS.add ("stdlib.W*");
924                EXCLUDE_GLOBS.add ("stdlib.X*");
925                EXCLUDE_GLOBS.add ("stdlib.Y*");
926                //EXCLUDE_GLOBS.add ("stdlib.Z*");      
927        }
928        /**
929         * Add an include pattern for drawing.  These are classes that should be shown in drawing and console logs, which would otherwise be excluded.
930         * The default is:
931         *
932         * <pre>
933         *  "java.util.*"
934         * </pre>
935         */
936        public static void addDrawingIncludePattern (String value) {
937                DRAWING_INCLUDE_GLOBS.add (value);
938        }
939        /**
940         * Remove an include pattern.
941         *
942         * @see addDrawingIncludePattern
943         */
944        public static void removeDrawingIncludePattern (String value) {
945                DRAWING_INCLUDE_GLOBS.remove (value);
946        }
947        protected static HashSet<String> DRAWING_INCLUDE_GLOBS;
948        static {
949                DRAWING_INCLUDE_GLOBS = new HashSet<> ();
950                DRAWING_INCLUDE_GLOBS.add ("java.util.*");
951        }
952        /**
953         * When the debugged program ends, create a graphviz file showing the call tree (default==false).
954         */
955        public static void drawCallTree (boolean value) { SHOW_CALL_TREE = value; }
956        protected static boolean SHOW_CALL_TREE = false;
957        /**
958         * Graphviz style for a call tree node.
959         */
960        public static void graphvizCallTreeBoxAttributes (String value) {
961                String v = (value == null || "".equals (value)) ? "" : "," + value;
962                GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = v;
963        }
964        protected static String GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES = ",shape=record";
965        /**
966         * Graphviz style for a call tree arrow.
967         */
968        public static void graphvizCallTreeArrowAttributes (String value) {
969                String v = (value == null || "".equals (value)) ? "" : "," + value;
970                GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = v;
971        }
972        protected static String GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES = ",fontsize=12";
973        /**
974         * Graphviz style for an array.
975         */
976        public static void graphvizArrayBoxAttributes (String value) {
977                String v = (value == null || "".equals (value)) ? "" : "," + value;
978                GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = v;
979        }
980        protected static String GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = ",shape=record,color=blue";
981        /**
982         * Graphviz style for an arrow from an array to an Object.
983         */
984        public static void graphvizArrayArrowAttributes (String value) {
985                String v = (value == null || "".equals (value)) ? "" : "," + value;
986                GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = v;
987        }
988        protected static String GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = ",fontsize=12,color=blue,arrowtail=dot,dir=both,tailclip=false";
989        /**
990         * Graphviz style for a frame.
991         */
992        public static void graphvizFrameBoxAttributes (String value) {
993                String v = (value == null || "".equals (value)) ? "" : "," + value;
994                GRAPHVIZ_FRAME_BOX_ATTRIBUTES = v;
995        }
996        protected static String GRAPHVIZ_FRAME_BOX_ATTRIBUTES = ",shape=record,color=red";
997        /**
998         * Graphviz style for an arrow from a frame to an Object.
999         */
1000        public static void graphvizFrameObjectArrowAttributes (String value) {
1001                String v = (value == null || "".equals (value)) ? "" : "," + value;
1002                GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES = v;
1003        }
1004        protected static String GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES = ",fontsize=12,color=red";
1005        /**
1006         * Graphviz style for an arrow from a return value to a frame.
1007         */
1008        public static void graphvizFrameReturnAttributes (String value) {
1009                String v = (value == null || "".equals (value)) ? "" : "," + value;
1010                GRAPHVIZ_FRAME_RETURN_ATTRIBUTES = v;
1011        }
1012        protected static String GRAPHVIZ_FRAME_RETURN_ATTRIBUTES = ",color=red";
1013        /**
1014         * Graphviz style for an arrow from an exception to a frame.
1015         */
1016        public static void graphvizFrameExceptionAttributes (String value) {
1017                String v = (value == null || "".equals (value)) ? "" : "," + value;
1018                GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES = v;
1019        }
1020        protected static String GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES = ",color=red";
1021        /**
1022         * Graphviz style for an arrow from a frame to another frame.
1023         */
1024        public static void graphvizFrameFrameArrowAttributes (String value) {
1025                String v = (value == null || "".equals (value)) ? "" : "," + value;
1026                GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES = v;
1027        }
1028        protected static String GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES = ",color=red,style=dashed";
1029        /**
1030         * Graphviz style for an object (non-array).
1031         */
1032        public static void graphvizObjectBoxAttributes (String value) {
1033                String v = (value == null || "".equals (value)) ? "" : "," + value;
1034                GRAPHVIZ_OBJECT_BOX_ATTRIBUTES = v;
1035        }
1036        protected static String GRAPHVIZ_OBJECT_BOX_ATTRIBUTES = ",shape=record,color=purple";
1037        /**
1038         * Graphviz style for a wrapper object (in simple form).
1039         */
1040        public static void graphvizWrapperBoxAttributes (String value) {
1041                String v = (value == null || "".equals (value)) ? "" : "," + value;
1042                GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES = v;
1043        }
1044        protected static String GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES = ",shape=ellipse,color=purple";
1045        /**
1046         * Graphviz style for a wrapper object (in simple form).
1047         */
1048        public static void graphvizNodeBoxAttributes (String value) {
1049                String v = (value == null || "".equals (value)) ? "" : "," + value;
1050                GRAPHVIZ_NODE_BOX_ATTRIBUTES = v;
1051        }
1052        protected static String GRAPHVIZ_NODE_BOX_ATTRIBUTES = ",shape=box,style=\"rounded\",color=purple";
1053        /**
1054         * Graphviz style for an arrow from an object to an object.
1055         */
1056        public static void graphvizObjectArrowAttributes (String value) {
1057                String v = (value == null || "".equals (value)) ? "" : "," + value;
1058                GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES = v;
1059        }
1060        protected static String GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES = ",fontsize=12,color=purple";
1061        /**
1062         * Extra Graphviz style for an arrow from an non-node to a node object.
1063         */
1064        public static void graphvizNodeArrowAttributes (String value) {
1065                String v = (value == null || "".equals (value)) ? "" : "," + value;
1066                GRAPHVIZ_NODE_ARROW_ATTRIBUTES = v;
1067        }
1068        protected static String GRAPHVIZ_NODE_ARROW_ATTRIBUTES = ""; 
1069        // Intention was to set this to ",constraint=false", but this creates errors in running DOT on the resulting gv file.   
1070        /**
1071         * Graphviz style for a static class.
1072         */
1073        public static void graphvizStaticClassBoxAttributes (String value) {
1074                String v = (value == null || "".equals (value)) ? "" : "," + value;
1075                GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES = v;
1076        }
1077        protected static String GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES = ",shape=record,color=orange";
1078        /**
1079         * Graphviz style for an arrow from a static class to an object.
1080         */
1081        public static void graphvizStaticClassArrowAttributes (String value) {
1082                String v = (value == null || "".equals (value)) ? "" : "," + value;
1083                GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES = v;
1084        }
1085        protected static String GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES = ",fontsize=12,color=orange";
1086        /**
1087         * Graphviz style for box labels.
1088         */
1089        public static void graphvizLabelBoxAttributes (String value) {
1090                String v = (value == null || "".equals (value)) ? "" : "," + value;
1091                GRAPHVIZ_LABEL_BOX_ATTRIBUTES = v;
1092        }
1093        protected static String GRAPHVIZ_LABEL_BOX_ATTRIBUTES = ",shape=none,color=black";
1094        /**
1095         * Graphviz style for arrow labels.
1096         */
1097        public static void graphvizLabelArrowAttributes (String value) {
1098                String v = (value == null || "".equals (value)) ? "" : "," + value;
1099                GRAPHVIZ_LABEL_ARROW_ATTRIBUTES = v;
1100        }
1101        protected static String GRAPHVIZ_LABEL_ARROW_ATTRIBUTES = ",color=black";
1102
1103        // Graphiz execution
1104        /**
1105         * Add a filesystem location to search for the dot executable that comes
1106         * with graphviz. The default is system dependent.
1107         */
1108        public static void graphvizAddPossibleDotLocation (String value) {
1109                GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (value);
1110        }
1111        protected static ArrayList<String> GRAPHVIZ_POSSIBLE_DOT_LOCATIONS;
1112        static {
1113                GRAPHVIZ_POSSIBLE_DOT_LOCATIONS = new ArrayList<> ();
1114                String os = System.getProperty ("os.name").toLowerCase ();
1115                if (os.startsWith ("win")) {
1116                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/Program Files (x86)/Graphviz2.38/bin/dot.exe");         // installer 32 bit
1117                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/Program Files/Graphviz/bin/dot.exe");                   // installer 64 bit
1118                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/ProgramData/chocolatey/bin/dot.exe");                   // choco
1119                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.home") + "/scoop/shims/dot.exe"); // scoop
1120                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-windows/bin/dot.exe");
1121                } else if (os.startsWith ("mac")) {
1122                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/opt/homebrew/bin/dot"); // homebrew
1123                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/opt/local/bin/dot");    // macports 
1124                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot");    // homebrew
1125                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot");          // native
1126                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-mac/bin/dot");
1127                } else {
1128                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot");
1129                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot");
1130                }
1131                // Annoyingly, the PATH is usually not what you want when running inside an IDE like eclipse, thus the nonsense above.
1132                String[] paths = System.getenv ("PATH").split(File.pathSeparator);
1133                for (String p : paths) 
1134                        GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (p + File.separator + "dot");
1135        }
1136        /**
1137         * Remove graphviz files for which graphic files have been successfully
1138         * generated.
1139         */
1140        public static void graphvizRemoveGvFiles (boolean value) {
1141                GRAPHVIZ_REMOVE_GV_FILES = value;
1142        }
1143        protected static boolean GRAPHVIZ_REMOVE_GV_FILES = true;
1144
1145        /**
1146         * Add classname to be drawn as a simple oval, with null as a dot.
1147         * Any classname with value as a suffix will be treated as a Node.
1148         * Default value includes "Node".
1149         */
1150        public static void graphvizAddNodeClass (String value) {
1151                GRAPHVIZ_NODE_CLASS.add (value);
1152        }
1153        /**
1154         * Remove a node class.
1155         *
1156         * @see graphvizAddNodeClass
1157         */
1158        public static void graphvizRemoveNodeClass (String value) {
1159                GRAPHVIZ_NODE_CLASS.remove (value);
1160        }
1161        /**
1162         * Remove all node classes.
1163         *
1164         * @see graphvizAddNodeClass
1165         */
1166        public static void showNodesAsRegularObjects () {
1167                GRAPHVIZ_NODE_CLASS = new ArrayList<> ();
1168        }
1169        protected static ArrayList<String> GRAPHVIZ_NODE_CLASS;
1170        static {
1171                GRAPHVIZ_NODE_CLASS = new ArrayList<> ();       
1172                GRAPHVIZ_NODE_CLASS.add ("Node");
1173        }
1174        /**
1175         * Show fully qualified class names (default==false). If
1176         * showPackageInClassName is true, then showOuterClassInClassName is ignored
1177         * (taken to be true).
1178         */
1179        public static void showPackageInClassName (boolean value) {
1180                SHOW_PACKAGE_IN_CLASS_NAME = value;
1181        }
1182        protected static boolean SHOW_PACKAGE_IN_CLASS_NAME = false;
1183        /**
1184         * Show static classes (default==true). 
1185         */
1186        public static void showStaticClasses (boolean value) {
1187                GRAPHVIZ_SHOW_STATIC_CLASSES = value;
1188        }
1189        protected static boolean GRAPHVIZ_SHOW_STATIC_CLASSES = true;
1190        /**
1191         * Include fully qualified class names (default==false).
1192         */
1193        public static void showOuterClassInClassName (boolean value) {
1194                SHOW_OUTER_CLASS_IN_CLASS_NAME = value;
1195        }
1196        protected static boolean SHOW_OUTER_CLASS_IN_CLASS_NAME = false;
1197        /**
1198         * Show the object type in addition to its id (default==false).
1199         */
1200        public static void consoleShowTypeInObjectName (boolean value) {
1201                CONSOLE_SHOW_TYPE_IN_OBJECT_NAME = value;
1202        }
1203        protected static boolean CONSOLE_SHOW_TYPE_IN_OBJECT_NAME = false;
1204        /**
1205         * The maximum number of displayed fields when printing an object on the
1206         * console (default==8).
1207         */
1208        public static void consoleMaxFields (int value) {
1209                CONSOLE_MAX_FIELDS = value;
1210        }
1211        protected static int CONSOLE_MAX_FIELDS = 8;
1212        /**
1213         * The maximum number of displayed elements when printing a primitive array
1214         * on the console (default==15).
1215         */
1216        public static void consoleMaxArrayElementsPrimitive (int value) {
1217                CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE = value;
1218        }
1219        protected static int CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE = 15;
1220        /**
1221         * The maximum number of displayed elements when printing an object array on
1222         * the console (default==8).
1223         */
1224        public static void consoleMaxArrayElementsObject (int value) {
1225                CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT = value;
1226        }
1227        protected static int CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT = 8;
1228        /**
1229         * Show object ids inside multidimensional arrays (default==false).
1230         */
1231        public static void consoleShowNestedArrayIds (boolean value) {
1232                CONSOLE_SHOW_NESTED_ARRAY_IDS = value;
1233        }
1234        protected static boolean CONSOLE_SHOW_NESTED_ARRAY_IDS = false;
1235        /**
1236         * Show fields introduced by the compiler (default==true);
1237         */
1238        public static void showSyntheticFields (boolean value) {
1239                SHOW_SYNTHETIC_FIELDS = value;
1240        }
1241        protected static boolean SHOW_SYNTHETIC_FIELDS = true;
1242        /**
1243         * Show methods introduced by the compiler (default==true);
1244         */
1245        public static void showSyntheticMethods (boolean value) {
1246                SHOW_SYNTHETIC_METHODS = value;
1247        }
1248        protected static boolean SHOW_SYNTHETIC_METHODS = true;
1249        /**
1250         * Show file names on the console (default==false).
1251         */
1252        public static void showFilenamesOnConsole (boolean value) {
1253                GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE = value;
1254        }
1255        protected static boolean GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE = false;
1256        /**
1257         * In graphviz, show field name in the label of an object (default==true).
1258         */
1259        public static void showFieldNamesInLabels (boolean value) {
1260                GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS = value;
1261        }
1262        protected static boolean GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS = true;
1263        /**
1264         * In graphviz, show field name in the label of a node object (default==false).
1265         */
1266        public static void showFieldNamesInNodeLabels (boolean value) {
1267                GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_LABELS = value;
1268        }
1269        protected static boolean GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_LABELS = false;
1270        /**
1271         * In graphviz, show field name on the arrow of a node object (default==true).
1272         */
1273        public static void showFieldNamesInNodeArrows (boolean value) {
1274                GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_ARROWS = value;
1275        }
1276        protected static boolean GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_ARROWS = true;
1277        /**
1278         * In graphviz, show object ids (default==false).
1279         */
1280        public static void showObjectIds (boolean value) {
1281                GRAPHVIZ_SHOW_OBJECT_IDS = value;
1282        }
1283        protected static boolean GRAPHVIZ_SHOW_OBJECT_IDS = false;
1284        /**
1285         * In graphviz, show object ids and redundant variables for arrows (default==false).
1286         * This also sets showBuiltInObjects to value.
1287         */
1288        public static void showObjectIdsRedundantly (boolean value) {
1289                Trace.showBuiltInObjects(value);
1290                GRAPHVIZ_SHOW_OBJECT_IDS = value;
1291                GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY = value;
1292        }
1293        protected static boolean GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY = false;
1294        /**
1295         * In graphviz, show stack frame numbers (default==true).
1296         */
1297        public static void showFrameNumbers (boolean value) {
1298                GRAPHVIZ_SHOW_FRAME_NUMBERS = value;
1299        }
1300        protected static boolean GRAPHVIZ_SHOW_FRAME_NUMBERS = true;
1301        /**
1302         * In graphviz, show null fields (default==true).
1303         */
1304        public static void showNullFields (boolean value) {
1305                GRAPHVIZ_SHOW_NULL_FIELDS = value;
1306        }
1307        protected static boolean GRAPHVIZ_SHOW_NULL_FIELDS = true;
1308        /**
1309         * In graphviz, show null variables (default==true).
1310         */
1311        public static void showNullVariables (boolean value) {
1312                GRAPHVIZ_SHOW_NULL_VARIABLES = value;
1313        }
1314        protected static boolean GRAPHVIZ_SHOW_NULL_VARIABLES = true;
1315        /**
1316         * Include line number in graphviz filename (default==true).
1317         */
1318        public static void graphvizPutLineNumberInFilename (boolean value) {
1319                GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME = value;
1320        }
1321        protected static boolean GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME = true;
1322        /**
1323         * Do not display any fields with this name (default includes only
1324         * "$assertionsDisabled").
1325         */
1326        public static void addGraphvizIgnoredFields (String value) {
1327                GRAPHVIZ_IGNORED_FIELDS.add (value);
1328        }
1329        protected static ArrayList<String> GRAPHVIZ_IGNORED_FIELDS;
1330        static {
1331                GRAPHVIZ_IGNORED_FIELDS = new ArrayList<> ();
1332                GRAPHVIZ_IGNORED_FIELDS.add ("$assertionsDisabled");
1333        }
1334        /**
1335         * Set the graphviz attributes for objects of the given class.
1336         */
1337        public static void graphvizSetObjectAttribute (Class<?> cz, String attrib) {
1338                Graphviz.objectAttributeMap.put (cz.getName (), attrib);
1339        }
1340        /**
1341         * Set the graphviz attributes for objects of the given class.
1342         */
1343        public static void graphvizSetStaticClassAttribute (Class<?> cz, String attrib) {
1344                Graphviz.staticClassAttributeMap.put (cz.getName (), attrib);
1345        }
1346        /**
1347         * Set the graphviz attributes for frames of the given class.
1348         */
1349        public static void graphvizSetFrameAttribute (Class<?> cz, String attrib) {
1350                Graphviz.frameAttributeMap.put (cz.getName (), attrib);
1351        }
1352        /**
1353         * Set the graphviz attributes for all fields with the given name.
1354         */
1355        public static void graphvizSetFieldAttribute (String field, String attrib) {
1356                Graphviz.fieldAttributeMap.put (field, attrib);
1357        }
1358        protected static String BAD_ERROR_MESSAGE = "\n!!!! This shouldn't happen! \n!!!! Please contact your instructor or the author of " + Trace.class.getCanonicalName ();
1359
1360        // ---------------------- Launch the JVM  ----------------------------------
1361
1362        // Set up a launching connection to the JVM
1363        private static VirtualMachine launchConnect (String mainClassName, String[] args) {
1364                // concatenate all the tracer's input arguments into a single string
1365                StringBuffer sb = new StringBuffer ();
1366                for (int i = 0; i < args.length; i++) {
1367                                sb.append (args[i] + " ");
1368                }
1369                String argsString = sb.toString ();
1370
1371                LaunchingConnector conn = null;
1372                Map<String, ? extends Argument> connArgs = null;
1373
1374                if (Graphviz.isWindows()) {
1375                        System.out.println ("here");
1376                        conn = Bootstrap.virtualMachineManager ().defaultConnector(); //"com.sun.jdi.CommandLineLaunch"
1377                        try {
1378                                connArgs = conn.defaultArguments ();
1379                                connArgs.get ("main").setValue (mainClassName);
1380                                connArgs.get ("options").setValue (argsString);
1381                                StdOut.println(connArgs);
1382                                //TODO print default arguments (it's a map) and find the classpath
1383                        } catch (NullPointerException e) {
1384                                throw new Error ("\n!!!! Bad launching connector");
1385                        }
1386
1387                } else {
1388                        // The default launcher works fine for windows, which uses shared memory
1389                        // for communication between the processes.
1390                        // The default launcher fails on unix-like systems, which uses sockets
1391                        // for communication between the processes.
1392                        // In particular, the communication is set up using hostname; this causes 
1393                        // communication to fail when the hostname is not routable in DNS or /etc/hosts
1394                        // The problem is in the file com/sun/tools/jdiSocketTransportService$SocketListenKey.java
1395                        // To fix, you just need to comment out the try block with "address = InetAddress.getLocalHost ();"
1396                        // As of Java 9, this fix is not available to outsiders, due to the module system
1397                        //                      
1398                        // The use of the raw launcher here is reverse engineered from 
1399                        //  com.sun.tools.jdi.SunCommandLineLauncher
1400                        //  com.sun.tools.jdi.RawCommandLineLauncher
1401                        //
1402                        // The raw connector has these attributes:
1403                        //   command = "" : Raw command to start the debugged application VM
1404                        //   address = "" : Address from which to listen for a connection after the raw command is run
1405                        //   quote = "\"" : Character used to combine space-delimited text into a single command line argument
1406                        //
1407                        // The default connector has these attributes:
1408                        //   home = System.getProperty("java.home") : Home directory of the SDK or runtime environment used to launch the application
1409                        //   options = ""    : Launched VM options
1410                        //   main = ""       : Main class and arguments, or if -jar is an option, the main jar file and arguments
1411                        //   suspend = true  : All threads will be suspended before execution of main
1412                        //   quote = "\""    : Character used to combine space-delimited text into a single command line argument
1413                        //   vmexec = "java" : Name of the Java VM launcher
1414                        String fs = System.getProperty ("file.separator");
1415                        String command = 
1416                                        "\"" + System.getProperty ("java.home") + fs + "bin" + fs + "java" + "\" " +
1417                                                        "-Xdebug " +
1418                                                        "-Xrunjdwp:transport=dt_socket,address=127.0.0.1:8900,suspend=y " + 
1419                                                        argsString +
1420                                                        " " +
1421                                                        mainClassName +
1422                                                        "";
1423                        //System.out.println (command);
1424                        for (LaunchingConnector lc : Bootstrap.virtualMachineManager ().launchingConnectors ()) {
1425                                if (lc.name ().equals ("com.sun.jdi.RawCommandLineLaunch")) conn = lc;
1426                        }
1427                        try {
1428                                connArgs = conn.defaultArguments ();
1429                                connArgs.get ("command").setValue (command);
1430                                connArgs.get ("address").setValue ("127.0.0.1:8900");
1431                        } catch (NullPointerException e) {
1432                                throw new Error ("\n!!!! Bad launching connector");
1433                        }
1434                }
1435
1436                VirtualMachine vm = null;
1437                try {
1438                        vm = conn.launch (connArgs); // launch the JVM and connect to it
1439                } catch (IOException e) {
1440                        throw new Error ("\n!!!! Unable to launch JVM: " + e);
1441                } catch (IllegalConnectorArgumentsException e) {
1442                        throw new Error ("\n!!!! Internal error: " + e);
1443                } catch (VMStartException e) {
1444                        throw new Error ("\n!!!! JVM failed to start: " + e.getMessage ());
1445                }
1446
1447                return vm;
1448        }
1449
1450        // monitor the JVM running the application
1451        private static void monitorJVM (VirtualMachine vm) {
1452                // start JDI event handler which displays trace info
1453                JDIEventMonitor watcher = new JDIEventMonitor (vm);
1454                watcher.start ();
1455
1456                // redirect VM's output and error streams to the system output and error streams
1457                Process process = vm.process ();
1458                Thread errRedirect = new StreamRedirecter ("error reader", process.getErrorStream (), System.err);
1459                Thread outRedirect = new StreamRedirecter ("output reader", process.getInputStream (), System.out);
1460                errRedirect.start ();
1461                outRedirect.start ();
1462
1463                vm.resume (); // start the application
1464
1465                try {
1466                        watcher.join (); // Wait. Shutdown begins when the JDI watcher terminates
1467                        errRedirect.join (); // make sure all the stream outputs have been forwarded before we exit
1468                        outRedirect.join ();
1469                } catch (InterruptedException e) {}
1470        }
1471}
1472
1473/**
1474 * StreamRedirecter is a thread which copies it's input to it's output and
1475 * terminates when it completes.
1476 *
1477 * @author Robert Field, September 2005
1478 * @author Andrew Davison, March 2009, ad@fivedots.coe.psu.ac.th
1479 */
1480/* private static */class StreamRedirecter extends Thread {
1481        private static final int BUFFER_SIZE = 2048;
1482        private final Reader in;
1483        private final Writer out;
1484
1485        public StreamRedirecter (String name, InputStream in, OutputStream out) {
1486                super (name);
1487                this.in = new InputStreamReader (in); // stream to copy from
1488                this.out = new OutputStreamWriter (out); // stream to copy to
1489                setPriority (Thread.MAX_PRIORITY - 1);
1490        }
1491
1492        // copy BUFFER_SIZE chars at a time
1493        public void run () {
1494                try {
1495                        char[] cbuf = new char[BUFFER_SIZE];
1496                        int count;
1497                        while ((count = in.read (cbuf, 0, BUFFER_SIZE)) >= 0)
1498                                out.write (cbuf, 0, count);
1499                        out.flush ();
1500                } catch (IOException e) {
1501                        System.err.println ("StreamRedirecter: " + e);
1502                }
1503        }
1504
1505}
1506
1507/**
1508 * Monitor incoming JDI events for a program running in the JVM and print out
1509 * trace/debugging information.
1510 *
1511 * This is a simplified version of EventThread.java from the Trace.java example
1512 * in the demo/jpda/examples.jar file in the JDK.
1513 *
1514 * Andrew Davison: The main addition is the use of the ShowCodes and ShowLines
1515 * classes to list the line being currently executed.
1516 *
1517 * James Riely: See comments in class Trace.
1518 *
1519 * @author Robert Field and Minoru Terada, September 2005
1520 * @author Iman_S, June 2008
1521 * @author Andrew Davison, ad@fivedots.coe.psu.ac.th, March 2009
1522 * @author James Riely, jriely@cs.depaul.edu, August 2014
1523 */
1524/* private static */class JDIEventMonitor extends Thread {
1525        // exclude events generated for these classes
1526        private final VirtualMachine vm; // the JVM
1527        private boolean connected = true; // connected to VM?
1528        private boolean vmDied; // has VM death occurred?
1529        private final JDIEventHandler printer = new Printer ();
1530
1531        public JDIEventMonitor (VirtualMachine jvm) {
1532                super ("JDIEventMonitor");
1533                vm = jvm;
1534                setEventRequests ();
1535        }
1536
1537        // Create and enable the event requests for the events we want to monitor in
1538        // the running program.
1539        //
1540        // Created here:
1541        //
1542        //    createThreadStartRequest()
1543        //    createThreadDeathRequest()
1544        //    createClassPrepareRequest()
1545        //    createClassUnloadRequest()
1546        //    createMethodEntryRequest()
1547        //    createMethodExitRequest()
1548        //    createExceptionRequest(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught)
1549        //    createMonitorContendedEnterRequest()
1550        //    createMonitorContendedEnteredRequest()
1551        //    createMonitorWaitRequest()
1552        //    createMonitorWaitedRequest()
1553        //
1554        // Created when class is loaded:
1555        //
1556        //    createModificationWatchpointRequest(Field field)
1557        //
1558        // Created when thread is started:
1559        //
1560        //    createStepRequest(ThreadReference thread, int size, int depth)
1561        //
1562        // Unused:
1563        //
1564        //    createAccessWatchpointRequest(Field field)
1565        //    createBreakpointRequest(Location location)
1566        //
1567        // Unnecessary:
1568        //
1569        //    createVMDeathRequest() // these happen even without being requested
1570        //
1571        private void setEventRequests () {
1572                EventRequestManager mgr = vm.eventRequestManager ();
1573                {
1574                        ThreadStartRequest x = mgr.createThreadStartRequest (); // report thread starts
1575                        x.enable ();
1576                }
1577                {
1578                        ThreadDeathRequest x = mgr.createThreadDeathRequest (); // report thread deaths
1579                        x.enable ();
1580                }
1581                {
1582                        ClassPrepareRequest x = mgr.createClassPrepareRequest (); // report class loads
1583                        for (String s : Trace.EXCLUDE_GLOBS)
1584                                x.addClassExclusionFilter (s);
1585                        // x.setSuspendPolicy(EventRequest.SUSPEND_ALL);
1586                        x.enable ();
1587                }
1588                {
1589                        ClassUnloadRequest x = mgr.createClassUnloadRequest (); // report class unloads
1590                        for (String s : Trace.EXCLUDE_GLOBS)
1591                                x.addClassExclusionFilter (s);
1592                        // x.setSuspendPolicy(EventRequest.SUSPEND_ALL);
1593                        x.enable ();
1594                }
1595                {
1596                        MethodEntryRequest x = mgr.createMethodEntryRequest (); // report method entries
1597                        for (String s : Trace.EXCLUDE_GLOBS)
1598                                x.addClassExclusionFilter (s);
1599                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1600                        x.enable ();
1601                }
1602                {
1603                        MethodExitRequest x = mgr.createMethodExitRequest (); // report method exits
1604                        for (String s : Trace.EXCLUDE_GLOBS)
1605                                x.addClassExclusionFilter (s);
1606                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1607                        x.enable ();
1608                }
1609                {
1610                        ExceptionRequest x = mgr.createExceptionRequest (null, true, true); // report all exceptions, caught and uncaught
1611                        for (String s : Trace.EXCLUDE_GLOBS)
1612                                x.addClassExclusionFilter (s);
1613                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1614                        x.enable ();
1615                }
1616                {
1617                        MonitorContendedEnterRequest x = mgr.createMonitorContendedEnterRequest ();
1618                        for (String s : Trace.EXCLUDE_GLOBS)
1619                                x.addClassExclusionFilter (s);
1620                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1621                        x.enable ();
1622                }
1623                {
1624                        MonitorContendedEnteredRequest x = mgr.createMonitorContendedEnteredRequest ();
1625                        for (String s : Trace.EXCLUDE_GLOBS)
1626                                x.addClassExclusionFilter (s);
1627                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1628                        x.enable ();
1629                }
1630                {
1631                        MonitorWaitRequest x = mgr.createMonitorWaitRequest ();
1632                        for (String s : Trace.EXCLUDE_GLOBS)
1633                                x.addClassExclusionFilter (s);
1634                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1635                        x.enable ();
1636                }
1637                {
1638                        MonitorWaitedRequest x = mgr.createMonitorWaitedRequest ();
1639                        for (String s : Trace.EXCLUDE_GLOBS)
1640                                x.addClassExclusionFilter (s);
1641                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1642                        x.enable ();
1643                }
1644        }
1645
1646        // process JDI events as they arrive on the event queue
1647        public void run () {
1648                EventQueue queue = vm.eventQueue ();
1649                while (connected) {
1650                        try {
1651                                EventSet eventSet = queue.remove ();
1652                                for (Event event : eventSet)
1653                                        handleEvent (event);
1654                                eventSet.resume ();
1655                        } catch (InterruptedException e) {
1656                                // Ignore
1657                        } catch (VMDisconnectedException discExc) {
1658                                handleDisconnectedException ();
1659                                break;
1660                        }
1661                }
1662                printer.printCallTree ();
1663        }
1664
1665        // process a JDI event
1666        private void handleEvent (Event event) {
1667                if (Trace.DEBUG) System.err.print (event.getClass ().getSimpleName ().replace ("EventImpl", ""));
1668
1669                // step event -- a line of code is about to be executed
1670                if (event instanceof StepEvent) {
1671                        stepEvent ((StepEvent) event);
1672                        return;
1673                }
1674
1675                // modified field event  -- a field is about to be changed
1676                if (event instanceof ModificationWatchpointEvent) {
1677                        modificationWatchpointEvent ((ModificationWatchpointEvent) event);
1678                        return;
1679                }
1680
1681                // method events
1682                if (event instanceof MethodEntryEvent) {
1683                        methodEntryEvent ((MethodEntryEvent) event);
1684                        return;
1685                }
1686                if (event instanceof MethodExitEvent) {
1687                        methodExitEvent ((MethodExitEvent) event);
1688                        return;
1689                }
1690                if (event instanceof ExceptionEvent) {
1691                        exceptionEvent ((ExceptionEvent) event);
1692                        return;
1693                }
1694
1695                // monitor events
1696                if (event instanceof MonitorContendedEnterEvent) {
1697                        monitorContendedEnterEvent ((MonitorContendedEnterEvent) event);
1698                        return;
1699                }
1700                if (event instanceof MonitorContendedEnteredEvent) {
1701                        monitorContendedEnteredEvent ((MonitorContendedEnteredEvent) event);
1702                        return;
1703                }
1704                if (event instanceof MonitorWaitEvent) {
1705                        monitorWaitEvent ((MonitorWaitEvent) event);
1706                        return;
1707                }
1708                if (event instanceof MonitorWaitedEvent) {
1709                        monitorWaitedEvent ((MonitorWaitedEvent) event);
1710                        return;
1711                }
1712
1713                // class events
1714                if (event instanceof ClassPrepareEvent) {
1715                        classPrepareEvent ((ClassPrepareEvent) event);
1716                        return;
1717                }
1718                if (event instanceof ClassUnloadEvent) {
1719                        classUnloadEvent ((ClassUnloadEvent) event);
1720                        return;
1721                }
1722
1723                // thread events
1724                if (event instanceof ThreadStartEvent) {
1725                        threadStartEvent ((ThreadStartEvent) event);
1726                        return;
1727                }
1728                if (event instanceof ThreadDeathEvent) {
1729                        threadDeathEvent ((ThreadDeathEvent) event);
1730                        return;
1731                }
1732
1733                // VM events
1734                if (event instanceof VMStartEvent) {
1735                        vmStartEvent ((VMStartEvent) event);
1736                        return;
1737                }
1738                if (event instanceof VMDeathEvent) {
1739                        vmDeathEvent ((VMDeathEvent) event);
1740                        return;
1741                }
1742                if (event instanceof VMDisconnectEvent) {
1743                        vmDisconnectEvent ((VMDisconnectEvent) event);
1744                        return;
1745                }
1746
1747                throw new Error ("\n!!!! Unexpected event type: " + event.getClass ().getCanonicalName ());
1748        }
1749
1750        // A VMDisconnectedException has occurred while dealing with another event.
1751        // Flush the event queue, dealing only with exit events (VMDeath,
1752        // VMDisconnect) so that things terminate correctly.
1753        private synchronized void handleDisconnectedException () {
1754                EventQueue queue = vm.eventQueue ();
1755                while (connected) {
1756                        try {
1757                                EventSet eventSet = queue.remove ();
1758                                for (Event event : eventSet) {
1759                                        if (event instanceof VMDeathEvent) vmDeathEvent ((VMDeathEvent) event);
1760                                        else if (event instanceof VMDisconnectEvent) vmDisconnectEvent ((VMDisconnectEvent) event);
1761                                }
1762                                eventSet.resume (); // resume the VM
1763                        } catch (InterruptedException e) {
1764                                // ignore
1765                        } catch (VMDisconnectedException e) {
1766                                // ignore
1767                        }
1768                }
1769        }
1770
1771        // ---------------------- VM event handling ----------------------------------
1772
1773        // Notification of initialization of a target VM. This event is received
1774        // before the main thread is started and before any application code has
1775        // been executed.
1776        private void vmStartEvent (VMStartEvent event) {
1777                vmDied = false;
1778                printer.vmStartEvent (event);
1779        }
1780
1781        // Notification of VM termination
1782        private void vmDeathEvent (VMDeathEvent event) {
1783                vmDied = true;
1784                printer.vmDeathEvent (event);
1785        }
1786
1787        // Notification of disconnection from the VM, either through normal
1788        // termination or because of an exception/error.
1789        private void vmDisconnectEvent (VMDisconnectEvent event) {
1790                connected = false;
1791                if (!vmDied) printer.vmDisconnectEvent (event);
1792        }
1793
1794        // -------------------- class event handling  ---------------
1795
1796        // a new class has been loaded
1797        private void classPrepareEvent (ClassPrepareEvent event) {
1798                ReferenceType type = event.referenceType ();
1799                String typeName = type.name ();
1800                if (Trace.CALLBACK_CLASS_NAME.equals (typeName) || Trace.GRAPHVIZ_CLASS_NAME.equals (typeName)) return;
1801                List<Field> fields = type.fields ();
1802
1803                // register field modification events
1804                EventRequestManager mgr = vm.eventRequestManager ();
1805                for (Field field : fields) {
1806                        ModificationWatchpointRequest req = mgr.createModificationWatchpointRequest (field);
1807                        for (String s : Trace.EXCLUDE_GLOBS)
1808                                req.addClassExclusionFilter (s);
1809                        req.setSuspendPolicy (EventRequest.SUSPEND_NONE);
1810                        req.enable ();
1811                }
1812                printer.classPrepareEvent (event);
1813
1814        }
1815        // a class has been unloaded
1816        private void classUnloadEvent (ClassUnloadEvent event) {
1817                if (!vmDied) printer.classUnloadEvent (event);
1818        }
1819
1820        // -------------------- thread event handling  ---------------
1821
1822        // a new thread has started running -- switch on single stepping
1823        private void threadStartEvent (ThreadStartEvent event) {
1824                ThreadReference thr = event.thread ();
1825                if (Format.ignoreThread (thr)) return;
1826                EventRequestManager mgr = vm.eventRequestManager ();
1827
1828                StepRequest sr = mgr.createStepRequest (thr, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
1829                sr.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1830
1831                for (String s : Trace.EXCLUDE_GLOBS)
1832                        sr.addClassExclusionFilter (s);
1833                sr.enable ();
1834                printer.threadStartEvent (event);
1835        }
1836
1837        // the thread is about to terminate
1838        private void threadDeathEvent (ThreadDeathEvent event) {
1839                ThreadReference thr = event.thread ();
1840                if (Format.ignoreThread (thr)) return;
1841                printer.threadDeathEvent (event);
1842        }
1843
1844        // -------------------- delegated --------------------------------
1845
1846        private void methodEntryEvent (MethodEntryEvent event) {
1847                printer.methodEntryEvent (event);
1848        }
1849        private void methodExitEvent (MethodExitEvent event) {
1850                printer.methodExitEvent (event);
1851        }
1852        private void exceptionEvent (ExceptionEvent event) {
1853                printer.exceptionEvent (event);
1854        }
1855        private void stepEvent (StepEvent event) {
1856                printer.stepEvent (event);
1857        }
1858        private void modificationWatchpointEvent (ModificationWatchpointEvent event) {
1859                printer.modificationWatchpointEvent (event);
1860        }
1861        private void monitorContendedEnterEvent (MonitorContendedEnterEvent event) {
1862                printer.monitorContendedEnterEvent (event);
1863        }
1864        private void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) {
1865                printer.monitorContendedEnteredEvent (event);
1866        }
1867        private void monitorWaitEvent (MonitorWaitEvent event) {
1868                printer.monitorWaitEvent (event);
1869        }
1870        private void monitorWaitedEvent (MonitorWaitedEvent event) {
1871                printer.monitorWaitedEvent (event);
1872        }
1873}
1874
1875/**
1876 * Printer for events. Prints and updates the ValueMap. Handles Graphviz drawing
1877 * requests.
1878 *
1879 * @author James Riely, jriely@cs.depaul.edu, August 2014
1880 */
1881/* private static */interface IndentPrinter {
1882        public void println (ThreadReference thr, String string);
1883}
1884
1885/* private static */interface JDIEventHandler {
1886        public void printCallTree ();
1887        /** Notification of target VM termination. */
1888        public void vmDeathEvent (VMDeathEvent event);
1889        /** Notification of disconnection from target VM. */
1890        public void vmDisconnectEvent (VMDisconnectEvent event);
1891        /** Notification of initialization of a target VM. */
1892        public void vmStartEvent (VMStartEvent event);
1893        /** Notification of a new running thread in the target VM. */
1894        public void threadStartEvent (ThreadStartEvent event);
1895        /** Notification of a completed thread in the target VM. */
1896        public void threadDeathEvent (ThreadDeathEvent event);
1897        /** Notification of a class prepare in the target VM. */
1898        public void classPrepareEvent (ClassPrepareEvent event);
1899        /** Notification of a class unload in the target VM. */
1900        public void classUnloadEvent (ClassUnloadEvent event);
1901        /** Notification of a field access in the target VM. */
1902        //public void accessWatchpointEvent (AccessWatchpointEvent event);
1903        /** Notification of a field modification in the target VM. */
1904        public void modificationWatchpointEvent (ModificationWatchpointEvent event);
1905        /** Notification of a method invocation in the target VM. */
1906        public void methodEntryEvent (MethodEntryEvent event);
1907        /** Notification of a method return in the target VM. */
1908        public void methodExitEvent (MethodExitEvent event);
1909        /** Notification of an exception in the target VM. */
1910        public void exceptionEvent (ExceptionEvent event);
1911        /** Notification of step completion in the target VM. */
1912        public void stepEvent (StepEvent event);
1913        /** Notification of a breakpoint in the target VM. */
1914        //public void breakpointEvent (BreakpointEvent event);
1915        /**
1916         * Notification that a thread in the target VM is attempting to enter a
1917         * monitor that is already acquired by another thread.
1918         */
1919        public void monitorContendedEnterEvent (MonitorContendedEnterEvent event);
1920        /**
1921         * Notification that a thread in the target VM is entering a monitor after
1922         * waiting for it to be released by another thread.
1923         */
1924        public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event);
1925        /**
1926         * Notification that a thread in the target VM is about to wait on a monitor
1927         * object.
1928         */
1929        public void monitorWaitEvent (MonitorWaitEvent event);
1930        /**
1931         * Notification that a thread in the target VM has finished waiting on an
1932         * monitor object.
1933         */
1934        public void monitorWaitedEvent (MonitorWaitedEvent event);
1935}
1936
1937/* private static */class Printer implements IndentPrinter, JDIEventHandler {
1938        private final Set<ReferenceType> staticClasses = new HashSet<> ();
1939        private final Map<ThreadReference, Value> returnValues = new HashMap<> ();
1940        private final Map<ThreadReference, Value> exceptionsMap = new HashMap<> ();
1941        private final ValueMap values = new ValueMap ();
1942        private final CodeMap codeMap = new CodeMap ();
1943        private final InsideIgnoredMethodMap boolMap = new InsideIgnoredMethodMap ();
1944
1945        public void monitorContendedEnterEvent (MonitorContendedEnterEvent event) {}
1946        public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) {}
1947        public void monitorWaitEvent (MonitorWaitEvent event) {}
1948        public void monitorWaitedEvent (MonitorWaitedEvent event) {}
1949
1950        public void vmStartEvent (VMStartEvent event) {
1951                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Started");
1952        }
1953        public void vmDeathEvent (VMDeathEvent event) {
1954                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Stopped");
1955        }
1956        public void vmDisconnectEvent (VMDisconnectEvent event) {
1957                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Disconnected application");
1958        }
1959        public void threadStartEvent (ThreadStartEvent event) {
1960                ThreadReference thr = event.thread ();
1961                values.stackCreate (thr);
1962                boolMap.addThread (thr);
1963                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread started: " + thr.name ());
1964        }
1965        public void threadDeathEvent (ThreadDeathEvent event) {
1966                ThreadReference thr = event.thread ();
1967                values.stackDestroy (thr);
1968                boolMap.removeThread (thr);
1969                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread stopped: " + thr.name ());
1970        }
1971        public void classPrepareEvent (ClassPrepareEvent event) {
1972
1973                ReferenceType ref = event.referenceType ();
1974
1975                List<Field> fields = ref.fields ();
1976                List<Method> methods = ref.methods ();
1977
1978                String filename;
1979                try {
1980                        filename = ref.sourcePaths (null).get (0); // get filename of the class
1981                        codeMap.addFile (filename);
1982                } catch (AbsentInformationException e) {
1983                        filename = "??";
1984                }
1985
1986                boolean hasConstructors = false;
1987                boolean hasObjectMethods = false;
1988                boolean hasClassMethods = false;
1989                boolean hasClassFields = false;
1990                boolean hasObjectFields = false;
1991                for (Method m : methods) {
1992                        if (Format.isConstructor (m)) hasConstructors = true;
1993                        if (Format.isObjectMethod (m)) hasObjectMethods = true;
1994                        if (Format.isClassMethod (m)) hasClassMethods = true;
1995                }
1996                for (Field f : fields) {
1997                        if (Format.isStaticField (f)) hasClassFields = true;
1998                        if (Format.isObjectField (f)) hasObjectFields = true;
1999                }
2000
2001                if (hasClassFields) {
2002                        staticClasses.add (ref);
2003                }
2004                if (Trace.CONSOLE_SHOW_CLASSES) {
2005                        println ("|||| loaded class: " + ref.name () + " from " + filename);
2006                        if (hasClassFields) {
2007                                println ("||||  class fields: ");
2008                                for (Field f : fields)
2009                                        if (Format.isStaticField (f)) println ("||||    " + Format.fieldToString (f));
2010                        }
2011                        if (hasClassMethods) {
2012                                println ("||||  class methods: ");
2013                                for (Method m : methods)
2014                                        if (Format.isClassMethod (m)) println ("||||    " + Format.methodToString (m, false));
2015                        }
2016                        if (hasConstructors) {
2017                                println ("||||  constructors: ");
2018                                for (Method m : methods)
2019                                        if (Format.isConstructor (m)) println ("||||    " + Format.methodToString (m, false));
2020                        }
2021                        if (hasObjectFields) {
2022                                println ("||||  object fields: ");
2023                                for (Field f : fields)
2024                                        if (Format.isObjectField (f)) println ("||||    " + Format.fieldToString (f));
2025                        }
2026                        if (hasObjectMethods) {
2027                                println ("||||  object methods: ");
2028                                for (Method m : methods)
2029                                        if (Format.isObjectMethod (m)) println ("||||    " + Format.methodToString (m, false));
2030                        }
2031                }
2032        }
2033        public void classUnloadEvent (ClassUnloadEvent event) {
2034                if (Trace.CONSOLE_SHOW_CLASSES) println ("|||| unloaded class: " + event.className ());
2035        }
2036
2037        public void methodEntryEvent (MethodEntryEvent event) {
2038                Method meth = event.method ();
2039                ThreadReference thr = event.thread ();
2040                String calledMethodClassname = meth.declaringType ().name ();
2041                //System.err.println (calledMethodClassname);
2042                //System.err.println (Trace.GRAPHVIZ_CLASS_NAME);
2043                if (Format.matchesExcludePrefix (calledMethodClassname)) return;
2044                if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return;
2045                if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return;
2046
2047                if (!Trace.CALLBACK_CLASS_NAME.equals (calledMethodClassname)) {
2048                        StackFrame currFrame = Format.getFrame (meth, thr);
2049                        values.stackPushFrame (currFrame, thr);
2050                        if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) {
2051                                println (thr, ">>>> " + Format.methodToString (meth, true)); // + "[" + thr.name () + "]");
2052                                printLocals (currFrame, thr);
2053                        }
2054                } else {
2055                        // COPY PASTE HORRORS HERE
2056                        boolMap.enteringIgnoredMethod (thr);
2057                        String name = meth.name ();
2058                        if (Trace.CALLBACK_CLEAR_CALL_TREE.equals (name)) {
2059                                values.clearCallTree();
2060                        } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHOD.equals (name)) {
2061                                List<StackFrame> frames;
2062                                try {
2063                                        frames = thr.frames ();
2064                                } catch (IncompatibleThreadStateException e) {
2065                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2066                                }
2067                                StackFrame currFrame = Format.getFrame (meth, thr);
2068                                List<LocalVariable> locals;
2069                                try {
2070                                        locals = currFrame.visibleVariables ();
2071                                } catch (AbsentInformationException e) {
2072                                        return;
2073                                }
2074                                StringReference obj = (StringReference) currFrame.getValue (locals.get (0));
2075                                Trace.drawStepsOfMethodBegin (obj.value());
2076                                returnValues.put (thr, null);
2077                        } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHODS.equals (name)) {
2078                                List<StackFrame> frames;
2079                                try {
2080                                        frames = thr.frames ();
2081                                } catch (IncompatibleThreadStateException e) {
2082                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2083                                }
2084                                StackFrame currFrame = Format.getFrame (meth, thr);
2085                                List<LocalVariable> locals;
2086                                try {
2087                                        locals = currFrame.visibleVariables ();
2088                                } catch (AbsentInformationException e) {
2089                                        return;
2090                                }
2091                                ArrayReference arr = (ArrayReference) currFrame.getValue (locals.get (0)); 
2092                                for (int i = arr.length() - 1; i >= 0; i--) {
2093                                        StringReference obj = (StringReference) arr.getValue (i);
2094                                        Trace.drawStepsOfMethodBegin (obj.value());
2095                                }
2096                                returnValues.put (thr, null);
2097                        } else if (Trace.CALLBACK_DRAW_STEPS_BEGIN.equals (name)) {
2098                                Trace.GRAPHVIZ_SHOW_STEPS = true;
2099                                returnValues.put (thr, null);
2100                        } else if (Trace.CALLBACK_DRAW_STEPS_END.equals (name)) {
2101                                Trace.drawStepsOfMethodEnd ();
2102                        } else if (Trace.CALLBACKS.contains (name)) {
2103                                //System.err.println (calledMethodClassname + ":" + Trace.SPECIAL_METHOD_NAME + ":" + meth.name ());
2104                                StackFrame frame;
2105                                try {
2106                                        frame = thr.frame (1);
2107                                } catch (IncompatibleThreadStateException e) {
2108                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2109                                }
2110                                Location loc = frame.location ();
2111                                String label = Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ());
2112                                drawGraph (label, thr, meth);
2113                                if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ());
2114                        }
2115                }
2116        }
2117        public void methodExitEvent (MethodExitEvent event) {
2118                ThreadReference thr = event.thread ();
2119                Method meth = event.method ();
2120                String calledMethodClassname = meth.declaringType ().name ();
2121                if (Format.matchesExcludePrefix (calledMethodClassname)) return;
2122                if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return;
2123                if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return;
2124                if (boolMap.leavingIgnoredMethod (thr)) return;
2125                if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) {
2126                        Type returnType;
2127                        try {
2128                                returnType = meth.returnType ();
2129                        } catch (ClassNotLoadedException e) {
2130                                returnType = null;
2131                        }
2132                        if (returnType instanceof VoidType) {
2133                                println (thr, "<<<< " + Format.methodToString (meth, true));
2134                        } else {
2135                                println (thr, "<<<< " + Format.methodToString (meth, true) + " : " + Format.valueToString (event.returnValue ()));
2136                        }
2137                }
2138                values.stackPopFrame (thr);
2139                StackFrame currFrame = Format.getFrame (meth, thr);
2140                if (meth.isConstructor ()) {
2141                        returnValues.put (thr, currFrame.thisObject ());
2142                } else {
2143                        returnValues.put (thr, event.returnValue ());
2144                }
2145        }
2146        public void exceptionEvent (ExceptionEvent event) {
2147                ThreadReference thr = event.thread ();
2148                try {
2149                        StackFrame currentFrame = thr.frame (0);
2150                } catch (IncompatibleThreadStateException e) {
2151                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2152                }
2153                ObjectReference exception = event.exception ();
2154                Location catchLocation = event.catchLocation ();
2155                //String name = Format.objectToStringLong (exception);
2156                String message = "()";
2157                Field messageField = exception.referenceType ().fieldByName ("detailMessage");
2158                if (messageField != null) {
2159                        Value value = exception.getValue (messageField);
2160                        if (value != null) {
2161                                message = "(" + value.toString () + ")";
2162                        }
2163                }
2164                String name = Format.shortenFullyQualifiedName (exception.referenceType ().name ()) + message;
2165
2166                if (catchLocation == null) {
2167                        // uncaught exception
2168                        if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! UNCAUGHT EXCEPTION: " + name);
2169                        if (Trace.GRAPHVIZ_SHOW_STEPS) Graphviz.drawFramesCheck (null, null, event.exception (), null, staticClasses);
2170                } else {
2171                        if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! EXCEPTION: " + name);
2172                        if (Trace.GRAPHVIZ_SHOW_STEPS) exceptionsMap.put (thr, event.exception ());
2173                }
2174        }
2175        public void stepEvent (StepEvent event) {
2176                ThreadReference thr = event.thread ();
2177                if (boolMap.insideIgnoredMethod (thr)) {
2178                        //System.err.println ("ignored");
2179                        return;
2180                }
2181                values.maybeAdjustAfterException (thr);
2182
2183                Location loc = event.location ();
2184                String filename;
2185                try {
2186                        filename = loc.sourcePath ();
2187                } catch (AbsentInformationException e) {
2188                        return;
2189                }
2190                if (Trace.CONSOLE_SHOW_STEPS) {
2191                        values.stackUpdateFrame (event.location ().method (), thr, this);
2192                        int lineNumber = loc.lineNumber ();
2193                        if (Trace.CONSOLE_SHOW_STEPS_VERBOSE) {
2194                                println (thr, Format.shortenFilename (filename) + ":" + lineNumber + codeMap.show (filename, lineNumber));
2195                        } else {
2196                                printLineNum (thr, lineNumber);
2197                        }
2198                }
2199                if (Trace.GRAPHVIZ_SHOW_STEPS) {
2200                        try {
2201                                Graphviz.drawFramesCheck (Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ()), returnValues.get (thr),
2202                                                exceptionsMap.get (thr), thr.frames (), staticClasses);
2203                                if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ());
2204                                returnValues.put (thr, null);
2205                                exceptionsMap.put (thr, null);
2206                        } catch (IncompatibleThreadStateException e) {
2207                                throw new Error (Trace.BAD_ERROR_MESSAGE);
2208                        }
2209                }
2210
2211        }
2212        public void modificationWatchpointEvent (ModificationWatchpointEvent event) {
2213                ThreadReference thr = event.thread ();
2214                if (boolMap.insideIgnoredMethod (thr)) return;
2215                if (!Trace.CONSOLE_SHOW_STEPS) return;
2216                Field f = event.field ();
2217                Value value = event.valueToBe (); // value that _will_ be assigned
2218                String debug = Trace.DEBUG ? "#5" + "[" + thr.name () + "]" : "";
2219                Type type;
2220                try {
2221                        type = f.type ();
2222                } catch (ClassNotLoadedException e) {
2223                        type = null; // waiting for class to load
2224                }
2225
2226                if (value instanceof ArrayReference) {
2227                        if (f.isStatic ()) {
2228                                String name = Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name ();
2229                                if (values.registerStaticArray ((ArrayReference) value, name)) {
2230                                        println (thr, "  " + debug + "> " + name + " = " + Format.valueToString (value));
2231                                }
2232                        }
2233                        return; // array types are handled separately -- this avoids redundant printing
2234                }
2235                ObjectReference objRef = event.object ();
2236                if (objRef == null) {
2237                        println (thr, "  " + debug + "> " + Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name () + " = " + Format.valueToString (value));
2238                } else {
2239                        // changes to array references are printed by updateFrame
2240                        if (Format.tooManyFields (objRef)) {
2241                                println (thr, "  " + debug + "> " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (value));
2242                        } else {
2243                                println (thr, "  " + debug + "> this = " + Format.objectToStringLong (objRef));
2244                        }
2245                }
2246
2247        }
2248
2249        public void printCallTree () { values.printCallTree(); }
2250        private void drawGraph (String loc, ThreadReference thr, Method meth) {
2251                List<StackFrame> frames;
2252                try {
2253                        frames = thr.frames ();
2254                } catch (IncompatibleThreadStateException e) {
2255                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2256                }
2257                //setDrawPrefixFromParameter (Format.getFrame (meth, thr), meth);
2258                StackFrame currFrame = Format.getFrame (meth, thr);
2259                List<LocalVariable> locals;
2260                try {
2261                        locals = currFrame.visibleVariables ();
2262                } catch (AbsentInformationException e) {
2263                        return;
2264                }
2265                String name = meth.name ();
2266                if (Trace.CALLBACK_DRAW_THIS_FRAME.equals (name)) {
2267                        Graphviz.drawFrames (1, 2, loc, null, null, frames, staticClasses, false);
2268                } else if (Trace.CALLBACK_DRAW_ALL_FRAMES_AND_STATICS.equals (name)) {
2269                        Graphviz.drawFrames (1, frames==null?0:frames.size(), loc, null, null, frames, staticClasses, true);
2270                } else if (Trace.CALLBACK_DRAW_ALL_FRAMES.equals (name) || locals.size () == 0) {
2271                        Graphviz.drawFrames (1, frames==null?0:frames.size(), loc, null, null, frames, staticClasses, false);
2272                } else if (Trace.CALLBACK_DRAW_OBJECT.equals (name)) {
2273                        ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (0));
2274                        Map<String, ObjectReference> objects = new HashMap<> ();
2275                        objects.put (Graphviz.PREFIX_UNUSED_LABEL, obj);
2276                        Graphviz.drawObjects (loc, objects);
2277                } else if (Trace.CALLBACK_DRAW_OBJECT_NAMED.equals (name)) {
2278                        StringReference str = (StringReference) currFrame.getValue (locals.get (0));
2279                        ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (1));
2280                        Map<String, ObjectReference> objects = new HashMap<> ();
2281                        objects.put (str.value (), obj);
2282                        Graphviz.drawObjects (loc, objects);
2283                } else if (Trace.CALLBACK_DRAW_OBJECTS_NAMED.equals (name)) {
2284                        ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0));
2285                        Map<String, ObjectReference> objects = new HashMap<> ();
2286                        int n = args.length ();
2287                        if (n % 2 != 0) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED + " requires an even number of parameters, alternating strings and objects.");
2288                        for (int i = 0; i < n; i += 2) {
2289                                Value str = args.getValue (i);
2290                                if (!(str instanceof StringReference)) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED
2291                                                + " requires an even number of parameters, alternating strings and objects.");
2292                                objects.put (((StringReference) str).value (), (ObjectReference) args.getValue (i + 1));
2293                        }
2294                        Graphviz.drawObjects (loc, objects);
2295                } else {
2296                        ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0));
2297                        Map<String, ObjectReference> objects = new HashMap<> ();
2298                        int n = args.length ();
2299                        for (int i = 0; i < n; i++) {
2300                                objects.put (Graphviz.PREFIX_UNUSED_LABEL + i, (ObjectReference) args.getValue (i));
2301                        }
2302                        Graphviz.drawObjects (loc, objects);
2303                }
2304        }
2305        // This method was used to set the filename from the parameter of the draw method.
2306        // Not using this any more.
2307        //      private void setDrawPrefixFromParameter (StackFrame currFrame, Method meth) {
2308        //              String prefix = null;
2309        //              List<LocalVariable> locals;
2310        //              try {
2311        //                      locals = currFrame.visibleVariables ();
2312        //              } catch (AbsentInformationException e) {
2313        //                      return;
2314        //              }
2315        //              if (locals.size () >= 1) {
2316        //                      Value v = currFrame.getValue (locals.get (0));
2317        //                      if (!(v instanceof StringReference)) throw new Error ("\n!!!! " + meth.name () + " must have at most a single parameter."
2318        //                                      + "\n!!!! The parameter must be of type String");
2319        //                      prefix = ((StringReference) v).value ();
2320        //                      if (prefix != null) {
2321        //                              Graphviz.setOutputFilenamePrefix (prefix);
2322        //                      }
2323        //              }
2324        //      }
2325
2326        // ---------------------- print locals ----------------------------------
2327
2328        private void printLocals (StackFrame currFrame, ThreadReference thr) {
2329                List<LocalVariable> locals;
2330                try {
2331                        locals = currFrame.visibleVariables ();
2332                } catch (AbsentInformationException e) {
2333                        return;
2334                }
2335                String debug = Trace.DEBUG ? "#3" : "";
2336
2337                ObjectReference objRef = currFrame.thisObject (); // get 'this' object
2338                if (objRef != null) {
2339                        if (Format.tooManyFields (objRef)) {
2340                                println (thr, "  " + debug + "this: " + Format.objectToStringShort (objRef));
2341                                ReferenceType type = objRef.referenceType (); // get type (class) of object
2342                                List<Field> fields; // use allFields() to include inherited fields
2343                                try {
2344                                        fields = type.fields ();
2345                                } catch (ClassNotPreparedException e) {
2346                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2347                                }
2348
2349                                //println (thr, "  fields: ");
2350                                for (Field f : fields) {
2351                                        if (!Format.isObjectField (f)) continue;
2352                                        println (thr, "  " + debug + "| " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f)));
2353                                }
2354                                if (locals.size () > 0) println (thr, "  locals: ");
2355                        } else {
2356                                println (thr, "  " + debug + "| this = " + Format.objectToStringLong (objRef));
2357                        }
2358                }
2359                for (LocalVariable l : locals)
2360                        println (thr, "  " + debug + "| " + l.name () + " = " + Format.valueToString (currFrame.getValue (l)));
2361        }
2362
2363        // ---------------------- indented printing ----------------------------------
2364
2365        private boolean atNewLine = true;
2366        private static PrintStream out = System.out;
2367        public static void setFilename (String s) {
2368                try {
2369                        Printer.out = new PrintStream (s);
2370                } catch (FileNotFoundException e) {
2371                        System.err.println ("Attempting setFilename \"" + s + "\"");
2372                        System.err.println ("Cannot open file \"" + s + "\" for writing; using the console for output.");
2373                }
2374        }
2375        public static void setFilename () {
2376                Printer.out = System.out;
2377        }
2378        public void println (String string) {
2379                if (!atNewLine) {
2380                        atNewLine = true;
2381                        Printer.out.println ();
2382                }
2383                Printer.out.println (string);
2384        }
2385        public void println (ThreadReference thr, String string) {
2386                if (!atNewLine) {
2387                        atNewLine = true;
2388                        Printer.out.println ();
2389                }
2390                if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ());
2391                int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0;
2392                for (int i = 1; i < numFrames; i++)
2393                        Printer.out.print ("  ");
2394                Printer.out.println (string);
2395        }
2396        private void printLinePrefix (ThreadReference thr, boolean showLinePrompt) {
2397                if (atNewLine) {
2398                        atNewLine = false;
2399                        if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ());
2400                        int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0;
2401                        for (int i = 1; i < numFrames; i++)
2402                                Printer.out.print ("  ");
2403                        if (showLinePrompt) Printer.out.print ("  Line: ");
2404                }
2405        }
2406        public void printLineNum (ThreadReference thr, int lineNumber) {
2407                printLinePrefix (thr, true);
2408                Printer.out.print (lineNumber + " ");
2409        }
2410        public void printDrawEvent (ThreadReference thr, String filename) {
2411                printLinePrefix (thr, false);
2412                Printer.out.print ("#" + filename + "# ");
2413        }
2414}
2415
2416/**
2417 * Code for formatting values and other static utilities.
2418 *
2419 * @author James Riely, jriely@cs.depaul.edu, August 2014
2420 */
2421/* private static */class Format {
2422        private Format () {}; // noninstantiable class
2423
2424        public static StackFrame getFrame (Method meth, ThreadReference thr) {
2425                Type methDeclaredType = meth.declaringType ();
2426                int frameNumber = -1;
2427                StackFrame currFrame;
2428                try {
2429                        do {
2430                                frameNumber++;
2431                                currFrame = thr.frame (frameNumber);
2432                        } while (methDeclaredType != currFrame.location ().declaringType ());
2433                } catch (IncompatibleThreadStateException e) {
2434                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2435                }
2436                return currFrame;
2437        }
2438        // http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
2439        public static String glob2regex (String glob) {
2440                StringBuilder regex = new StringBuilder ("^");
2441                for(int i = 0; i < glob.length(); ++i) {
2442                        final char c = glob.charAt(i);
2443                        switch(c) {
2444                        case '*': regex.append (".*"); break;
2445                        case '.': regex.append ("\\."); break;
2446                        case '$': regex.append ("\\$"); break;
2447                        default: regex.append (c);
2448                        }
2449                }
2450                regex.append ('$');
2451                return regex.toString ();
2452        }
2453        private static final ArrayList<String> EXCLUDE_REGEX;
2454        private static final ArrayList<String> DRAWING_INCLUDE_REGEX;
2455        static {
2456                EXCLUDE_REGEX = new ArrayList<> ();
2457                for (String s : Trace.EXCLUDE_GLOBS) {
2458                        //System.err.println (glob2regex (s));
2459                        EXCLUDE_REGEX.add (glob2regex (s));
2460                }
2461                DRAWING_INCLUDE_REGEX = new ArrayList<> ();
2462                for (String s : Trace.DRAWING_INCLUDE_GLOBS) {
2463                        DRAWING_INCLUDE_REGEX.add (glob2regex (s));
2464                }
2465        }
2466        public static boolean matchesExcludePrefix (String typeName) {
2467                //System.err.println (typeName + ":" + Trace.class.getName());
2468                if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && "java.lang.String".equals (typeName)) return false;
2469                // don't explore objects on the exclude list
2470                for (String regex : Format.EXCLUDE_REGEX)
2471                        if (typeName.matches (regex)) return true;
2472                return false;
2473        }
2474        public static boolean matchesExcludePrefixShow (String typeName) {
2475                for (String regex : Format.DRAWING_INCLUDE_REGEX)
2476                        if (typeName.matches (regex)) return false;
2477                return matchesExcludePrefix (typeName);
2478        }
2479        public static String valueToString (Value value) {
2480                return valueToString (false, new HashSet<> (), value);
2481        }
2482        private static String valueToString (boolean inArray, Set<Value> visited, Value value) {
2483                if (value == null) return "null";
2484                if (value instanceof PrimitiveValue) return value.toString ();
2485                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString ();
2486                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return wrapperToString ((ObjectReference) value);
2487                return objectToStringLong (inArray, visited, (ObjectReference) value);
2488        }
2489        public static String valueToStringShort (Value value) {
2490                if (value == null) return "null";
2491                if (value instanceof PrimitiveValue) return value.toString ();
2492                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString ();
2493                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return value.toString (); //wrapperToString ((ObjectReference) value);
2494                return objectToStringShort ((ObjectReference) value);
2495        }
2496        public static boolean isWrapper (Type type) {
2497                if (!(type instanceof ReferenceType)) return false;
2498                if (type instanceof ArrayType) return false;
2499                String fqn = type.name ();
2500                if (!fqn.startsWith ("java.lang.")) return false;
2501                String className = fqn.substring (10);
2502                if (className.equals ("String")) return false;
2503                return (className.equals ("Integer") || className.equals ("Double") || className.equals ("Float") || className.equals ("Long") || className.equals ("Character")
2504                                || className.equals ("Short") || className.equals ("Byte") || className.equals ("Boolean"));
2505        }
2506        public static String wrapperToString (ObjectReference obj) {
2507                Object xObject;
2508                if (obj == null) return "null";
2509                ReferenceType cz = (ReferenceType) obj.type ();
2510                String fqn = cz.name ();
2511                String className = fqn.substring (10);
2512                Field field = cz.fieldByName ("value");
2513                return obj.getValue (field).toString ();
2514        }
2515        public static String objectToStringShort (ObjectReference objRef) {
2516                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (objRef.type ().name ()) + "@" + objRef.uniqueID ();
2517                else return "@" + objRef.uniqueID ();
2518        }
2519        private static String emptyArrayToStringShort (ArrayReference arrayRef, int length) {
2520                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) {
2521                        String classname = shortenFullyQualifiedName (arrayRef.type ().name ());
2522                        return classname.substring (0, classname.indexOf ("[")) + "[" + length + "]@" + arrayRef.uniqueID ();
2523                } else {
2524                        return "@" + arrayRef.uniqueID ();
2525                }
2526        }
2527        private static String nonemptyArrayToStringShort (ArrayReference arrayRef, int length) {
2528                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (arrayRef.getValue (0).type ().name ()) + "[" + length + "]@" + arrayRef.uniqueID ();
2529                else return "@" + arrayRef.uniqueID ();
2530        }
2531
2532        public static String objectToStringLong (ObjectReference objRef) {
2533                return objectToStringLong (false, new HashSet<> (), objRef);
2534        }
2535        private static String objectToStringLong (boolean inArray, Set<Value> visited, ObjectReference objRef) {
2536                if (!visited.add (objRef)) return objectToStringShort (objRef);
2537                StringBuilder result = new StringBuilder ();
2538                if (objRef == null) {
2539                        return "null";
2540                } else if (objRef instanceof ArrayReference) {
2541                        ArrayReference arrayRef = (ArrayReference) objRef;
2542                        int length = arrayRef.length ();
2543                        if (length == 0 || arrayRef.getValue (0) == null) {
2544                                if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) {
2545                                        result.append (emptyArrayToStringShort (arrayRef, length));
2546                                        result.append (" ");
2547                                }
2548                                result.append ("[ ] ");
2549                        } else {
2550                                if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) {
2551                                        result.append (nonemptyArrayToStringShort (arrayRef, length));
2552                                        result.append (" ");
2553                                }
2554                                result.append ("[ ");
2555                                int max = (arrayRef.getValue (0) instanceof PrimitiveValue) ? Trace.CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE : Trace.CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT;
2556                                int i = 0;
2557                                while (i < length && i < max) {
2558                                        result.append (valueToString (true, visited, arrayRef.getValue (i)));
2559                                        i++;
2560                                        if (i < length) result.append (", ");
2561                                }
2562                                if (i < length) result.append ("...");
2563                                result.append (" ]");
2564                        }
2565                } else {
2566                        result.append (objectToStringShort (objRef));
2567                        ReferenceType type = objRef.referenceType (); // get type (class) of object
2568
2569                        // don't explore objects on the exclude list
2570                        if (!Format.matchesExcludePrefixShow (type.name ())) {
2571                                Iterator<Field> fields; // use allFields() to include inherited fields
2572                                try {
2573                                        fields = type.fields ().iterator ();
2574                                } catch (ClassNotPreparedException e) {
2575                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2576                                }
2577                                if (fields.hasNext ()) {
2578                                        result.append (" { ");
2579                                        int i = 0;
2580                                        while (fields.hasNext () && i < Trace.CONSOLE_MAX_FIELDS) {
2581                                                Field f = fields.next ();
2582                                                if (!isObjectField (f)) continue;
2583                                                if (i != 0) result.append (", ");
2584                                                result.append (f.name ());
2585                                                result.append ("=");
2586                                                result.append (valueToString (inArray, visited, objRef.getValue (f)));
2587                                                i++;
2588                                        }
2589                                        if (fields.hasNext ()) result.append ("...");
2590                                        result.append (" }");
2591                                }
2592                        }
2593                }
2594                return result.toString ();
2595        }
2596
2597        // ---------------------- static utilities ----------------------------------
2598
2599        public static boolean ignoreThread (ThreadReference thr) {
2600                if (thr.name ().equals ("Signal Dispatcher") || thr.name ().equals ("DestroyJavaVM") || thr.name ().startsWith ("AWT-")) return true; // ignore AWT threads
2601                if (thr.threadGroup ().name ().equals ("system")) return true; // ignore system threads
2602                return false;
2603        }
2604
2605        public static boolean isStaticField (Field f) {
2606                if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false;
2607                return f.isStatic ();
2608        }
2609        public static boolean isObjectField (Field f) {
2610                if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false;
2611                return !f.isStatic ();
2612        }
2613        public static boolean isConstructor (Method m) {
2614                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2615                return m.isConstructor ();
2616        }
2617        public static boolean isObjectMethod (Method m) {
2618                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2619                return !m.isConstructor () && !m.isStatic ();
2620        }
2621        public static boolean isClassMethod (Method m) {
2622                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2623                return m.isStatic ();
2624        }
2625        public static boolean tooManyFields (ObjectReference objRef) {
2626                int count = 0;
2627                ReferenceType type = ((ReferenceType) objRef.type ());
2628                for (Field field : type.fields ())
2629                        if (isObjectField (field)) count++;
2630                return count > Trace.CONSOLE_MAX_FIELDS;
2631        }
2632        public static String shortenFullyQualifiedName (String fqn) {
2633                if (Trace.SHOW_PACKAGE_IN_CLASS_NAME || !fqn.contains (".")) return fqn;
2634                String className = fqn.substring (1 + fqn.lastIndexOf ("."));
2635                if (Trace.SHOW_OUTER_CLASS_IN_CLASS_NAME || !className.contains ("$")) return className;
2636                return className.substring (1 + className.lastIndexOf ("$"));
2637        }
2638        public static String shortenFilename (String fn) {
2639                if (!fn.contains ("/")) return fn;
2640                return fn.substring (1 + fn.lastIndexOf ("/"));
2641        }
2642        public static String fieldToString (Field f) {
2643                StringBuilder result = new StringBuilder ();
2644                if (f.isPrivate ()) result.append ("- ");
2645                if (f.isPublic ()) result.append ("+ ");
2646                if (f.isPackagePrivate ()) result.append ("~ ");
2647                if (f.isProtected ()) result.append ("# ");
2648                result.append (shortenFullyQualifiedName (f.name ()));
2649                result.append (" : ");
2650                result.append (shortenFullyQualifiedName (f.typeName ()));
2651                return result.toString ();
2652        }
2653        public static String methodToString (Method m, boolean showClass) {
2654                return methodToString (m, showClass, true, ".");
2655        }
2656        public static String methodToString (Method m, boolean showClass, boolean showParameters, String dotCharacter) {
2657                String className = shortenFullyQualifiedName (m.declaringType ().name ());
2658                StringBuilder result = new StringBuilder ();
2659                if (!showClass && showParameters) {
2660                        if (m.isPrivate ()) result.append ("- ");
2661                        if (m.isPublic ()) result.append ("+ ");
2662                        if (m.isPackagePrivate ()) result.append ("~ ");
2663                        if (m.isProtected ()) result.append ("# ");
2664                }
2665                if (m.isConstructor ()) {
2666                        result.append (className);
2667                } else if (m.isStaticInitializer ()) {
2668                        result.append (className);
2669                        result.append (".CLASS_INITIALIZER");
2670                        return result.toString ();
2671                } else {
2672                        if (showClass) {
2673                                result.append (className);
2674                                result.append (dotCharacter);
2675                        }
2676                        result.append (shortenFullyQualifiedName (m.name ()));
2677                }
2678                if (showParameters) {
2679                        result.append ("(");
2680                        Iterator<LocalVariable> vars;
2681                        try {
2682                                vars = m.arguments ().iterator ();
2683                                while (vars.hasNext ()) {
2684                                        result.append (shortenFullyQualifiedName (vars.next ().typeName ()));
2685                                        if (vars.hasNext ()) result.append (", ");
2686                                }
2687                        } catch (AbsentInformationException e) {
2688                                result.append ("??");
2689                        }
2690                        result.append (")");
2691                }
2692                //result.append (" from ");
2693                //result.append (m.declaringType ());
2694                return result.toString ();
2695        }
2696}
2697
2698/**
2699 * A map from filenames to file contents. Allows lines to be printed.
2700 *
2701 * changes: Riely inlined the "ShowLines" class.
2702 *
2703 * @author Andrew Davison, March 2009, ad@fivedots.coe.psu.ac.th
2704 * @author James Riely
2705 **/
2706/* private static */class CodeMap {
2707        private TreeMap<String, ArrayList<String>> listings = new TreeMap<> ();
2708
2709        // add filename-ShowLines pair to map
2710        public void addFile (String filename) {
2711                if (listings.containsKey (filename)) {
2712                        //System.err.println (filename + "already listed");
2713                        return;
2714                }
2715                String srcFilename = "src" + File.separator + filename;
2716                ArrayList<String> code = new ArrayList<> ();
2717                BufferedReader in = null;
2718                try {
2719                        in = new BufferedReader (new FileReader (srcFilename));
2720                        String line;
2721                        while ((line = in.readLine ()) != null)
2722                                code.add (line);
2723                } catch (IOException ex) {
2724                        System.err.println ("\n!!!! Could not read " + srcFilename);
2725                } finally {
2726                        try {
2727                                if (in != null) in.close ();
2728                        } catch (IOException e) {
2729                                throw new Error ("\n!!!! Problem reading " + srcFilename);
2730                        }
2731                }
2732                listings.put (filename, code);
2733                //println (filename + " added to listings");
2734        }
2735
2736        // return the specified line from filename
2737        public String show (String filename, int lineNumber) {
2738                ArrayList<String> code = listings.get (filename);
2739                if (code == null) return (filename + " not listed");
2740                if ((lineNumber < 1) || (lineNumber > code.size ())) return " [Could not load source file.]";
2741                return (code.get (lineNumber - 1));
2742        }
2743
2744}
2745
2746/**
2747 * Map from threads to booleans.
2748 *
2749 * @author James Riely, jriely@cs.depaul.edu, August 2014
2750 */
2751/* private static */class InsideIgnoredMethodMap {
2752        // Stack is probably unnecessary here.  A single boolean would do.
2753        private HashMap<ThreadReference, Stack<Boolean>> map = new HashMap<> ();
2754        public void removeThread (ThreadReference thr) {
2755                map.remove (thr);
2756        }
2757        public void addThread (ThreadReference thr) {
2758                Stack<Boolean> st = new Stack<> ();
2759                st.push (false);
2760                map.put (thr, st);
2761        }
2762        public void enteringIgnoredMethod (ThreadReference thr) {
2763                Stack<Boolean> insideStack = map.get (thr);
2764                insideStack.push (true);        
2765        }
2766        public boolean leavingIgnoredMethod (ThreadReference thr) {
2767                Stack<Boolean> insideStack = map.get (thr);
2768                boolean result = insideStack.peek ();
2769                if (result) insideStack.pop ();
2770                return result;
2771        }
2772        public boolean insideIgnoredMethod (ThreadReference thr) {
2773                return map.get (thr).peek ();
2774        }
2775}
2776
2777/** From sedgewick and wayne */
2778/* private static */class Stack<T> {
2779        private int N;
2780        private Node<T> first;
2781        private static class Node<T> {
2782                T item;
2783                Node<T> next;
2784        }
2785        public Stack () {
2786                first = null;
2787                N = 0;
2788        }
2789        public boolean isEmpty () {
2790                return first == null;
2791        }
2792        public int size () {
2793                return N;
2794        }
2795        public void push (T item) {
2796                Node<T> oldfirst = first;
2797                first = new Node<> ();
2798                first.item = item;
2799                first.next = oldfirst;
2800                N++;
2801        }
2802        public T pop () {
2803                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2804                T item = first.item;
2805                first = first.next;
2806                N--;
2807                return item;
2808        }
2809        public void pop (int n) {
2810                for (int i=n; i>0; i--)
2811                        pop ();
2812        }
2813        public T peek () {
2814                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2815                return first.item;
2816        }
2817}
2818
2819/**
2820 * Keeps track of values in order to spot changes. This keeps copies of stack
2821 * variables (frames) and arrays. Does not store objects, since direct changes
2822 * to fields can be trapped by the JDI.
2823 *
2824 * @author James Riely, jriely@cs.depaul.edu, August 2014
2825 */
2826/* private static */class ValueMap {
2827        private HashMap<ThreadReference, Stack<HashMap<LocalVariable, Value>>> stacks = new HashMap<> ();
2828        private HashMap<ArrayReference, Object[]> arrays = new HashMap<> ();
2829        private HashMap<ArrayReference, Object[]> staticArrays = new HashMap<> ();
2830        private HashMap<ArrayReference, String> staticArrayNames = new HashMap<> ();
2831        private CallTree callTree = new CallTree ();
2832        public int numThreads () {
2833                return stacks.size ();
2834        }
2835        public void clearCallTree () {
2836                callTree = new CallTree ();
2837        }
2838        public void printCallTree () {
2839                callTree.output ();
2840        }
2841        private static class CallTree {
2842                private HashMap<ThreadReference, Stack<String>> frameIdsMap = new HashMap<> ();
2843                private HashMap<ThreadReference, List<String>> gvStringsMap = new HashMap<> ();
2844                private int frameNumber = 0;
2845
2846                public void output () {
2847                        if (!Trace.SHOW_CALL_TREE) return;
2848                        Graphviz.drawStuff ("callTree", (out) -> {
2849                                out.println ("rankdir=LR;");
2850                                for (List<String> gvStrings : gvStringsMap.values ())
2851                                        for (String s : gvStrings) {
2852                                                out.println (s);
2853                                        }
2854                        });
2855                }
2856                public void pop (ThreadReference thr) {
2857                        if (!Trace.SHOW_CALL_TREE) return;
2858                        if (!Trace.drawStepsOfInternal (thr)) return;
2859
2860                        Stack<String> frameIds = frameIdsMap.get(thr);
2861                        if (!frameIds.isEmpty ())
2862                                frameIds.pop ();
2863                }
2864                public void push (StackFrame currFrame, ThreadReference thr) {
2865                        if (!Trace.SHOW_CALL_TREE) return;
2866                        if (!Trace.drawStepsOfInternal (thr)) return;
2867
2868                        Stack<String> frameIds = frameIdsMap.get(thr);
2869                        if (frameIds==null) { frameIds = new Stack<> (); frameIdsMap.put (thr, frameIds); }
2870                        List<String> gvStrings = gvStringsMap.get (thr);
2871                        if (gvStrings==null) { gvStrings = new LinkedList<> (); gvStringsMap.put (thr, gvStrings); }
2872
2873                        String currentFrameId = "f" + frameNumber++;
2874                        StringBuilder sb = new StringBuilder ();
2875                        Method method = currFrame.location ().method ();
2876                        sb.append (currentFrameId);
2877                        sb.append ("[label=\"");
2878                        if (method.isSynthetic ()) sb.append ("!");
2879                        if (!method.isStatic ()) {
2880                                sb.append (Graphviz.quote (Format.valueToStringShort (currFrame.thisObject ())));
2881                                sb.append (":");
2882                        }
2883                        sb.append (Graphviz.quote (Format.methodToString (method, true, false, ".")));
2884                        //sb.append (Graphviz.quote (method.name ()));
2885                        sb.append ("(");
2886                        List<LocalVariable> locals = null;
2887                        try { locals = currFrame.visibleVariables (); } catch (AbsentInformationException e) { }
2888                        if (locals != null) {
2889                                boolean first = true;
2890                                for (LocalVariable l : locals)
2891                                        if (l.isArgument ()) {
2892                                                if (!first) sb.append (", ");
2893                                                else first = false;
2894                                                String valString = Format.valueToString (currFrame.getValue (l));
2895                                                //Value val = currFrame.getValue (l);
2896                                                //String valString = val==null ? "null" : val.toString ();
2897                                                sb.append (Graphviz.quote (valString));
2898                                        }
2899                        }
2900                        sb.append (")\"");
2901                        sb.append (Trace.GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES);
2902                        sb.append ("];");
2903                        gvStrings.add (sb.toString ());
2904                        if (!frameIds.isEmpty ()) {
2905                                gvStrings.add (frameIds.peek () + " -> " + currentFrameId + "[label=\"\"" + Trace.GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES + "];");
2906                        }
2907                        frameIds.push (currentFrameId);
2908                }
2909        }
2910
2911        public boolean maybeAdjustAfterException (ThreadReference thr) {
2912                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2913
2914                // count the number of frames left
2915                int oldCount = stack.size ();
2916                int currentCount = 0;
2917                List<StackFrame> frames;
2918                try {
2919                        frames = thr.frames ();
2920                } catch (IncompatibleThreadStateException e) {
2921                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2922                }
2923
2924                for (StackFrame frame : frames) {
2925                        String calledMethodClassname = frame.location ().declaringType ().name ();
2926                        if (!Format.matchesExcludePrefix (calledMethodClassname)) currentCount++;
2927                }
2928
2929                if (oldCount > currentCount) {
2930                        for (int i = oldCount - currentCount; i > 0; i--) {
2931                                stack.pop ();
2932                                callTree.pop (thr);
2933                        }
2934                        return true;
2935                }
2936                return false;
2937        }
2938        public int numFrames (ThreadReference thr) {
2939                return stacks.get (thr).size ();
2940        }
2941        public void stackCreate (ThreadReference thr) {
2942                stacks.put (thr, new Stack<> ());
2943        }
2944        public void stackDestroy (ThreadReference thr) {
2945                stacks.remove (thr);
2946        }
2947        public void stackPushFrame (StackFrame currFrame, ThreadReference thr) {
2948                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
2949                callTree.push (currFrame, thr);
2950                List<LocalVariable> locals;
2951                try {
2952                        locals = currFrame.visibleVariables ();
2953                } catch (AbsentInformationException e) {
2954                        return;
2955                }
2956
2957                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2958                HashMap<LocalVariable, Value> frame = new HashMap<> ();
2959                stack.push (frame);
2960
2961                for (LocalVariable l : locals) {
2962                        Value v = currFrame.getValue (l);
2963                        frame.put (l, v);
2964                        if (v instanceof ArrayReference) registerArray ((ArrayReference) v);
2965                }
2966        }
2967
2968        public void stackPopFrame (ThreadReference thr) {
2969                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
2970                callTree.pop (thr);
2971                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2972                stack.pop ();
2973                // space leak in arrays HashMap: arrays never removed
2974        }
2975
2976        public void stackUpdateFrame (Method meth, ThreadReference thr, IndentPrinter printer) {
2977                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
2978                StackFrame currFrame = Format.getFrame (meth, thr);
2979                List<LocalVariable> locals;
2980                try {
2981                        locals = currFrame.visibleVariables ();
2982                } catch (AbsentInformationException e) {
2983                        return;
2984                }
2985                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2986                if (stack.isEmpty ()) {
2987                        throw new Error ("\n!!!! Frame empty: " + meth + " : " + thr);
2988                }
2989                HashMap<LocalVariable, Value> frame = stack.peek ();
2990
2991                String debug = Trace.DEBUG ? "#1" : "";
2992                for (LocalVariable l : locals) {
2993                        Value oldValue = frame.get (l);
2994                        Value newValue = currFrame.getValue (l);
2995                        if (valueHasChanged (oldValue, newValue)) {
2996                                frame.put (l, newValue);
2997                                if (newValue instanceof ArrayReference) registerArray ((ArrayReference) newValue);
2998                                String change = (oldValue == null) ? "|" : ">";
2999                                printer.println (thr, "  " + debug + change + " " + l.name () + " = " + Format.valueToString (newValue));
3000                        }
3001                }
3002
3003                ObjectReference thisObj = currFrame.thisObject ();
3004                if (thisObj != null) {
3005                        boolean show = Format.tooManyFields (thisObj);
3006                        if (arrayFieldHasChanged (show, thr, thisObj, printer) && !show) printer.println (thr, "  " + debug + "> this = " + Format.objectToStringLong (thisObj));
3007                }
3008                arrayStaticFieldHasChanged (true, thr, printer);
3009        }
3010
3011        public void registerArray (ArrayReference val) {
3012                if (!arrays.containsKey (val)) {
3013                        arrays.put (val, copyArray (val));
3014                }
3015        }
3016        public boolean registerStaticArray (ArrayReference val, String name) {
3017                if (!staticArrays.containsKey (val)) {
3018                        staticArrays.put (val, copyArray (val));
3019                        staticArrayNames.put (val, name);
3020                        return true;
3021                }
3022                return false;
3023        }
3024        private static Object[] copyArray (ArrayReference oldArrayReference) {
3025                Object[] newArray = new Object[oldArrayReference.length ()];
3026                for (int i = 0; i < newArray.length; i++) {
3027                        Value val = oldArrayReference.getValue (i);
3028                        if (val instanceof ArrayReference) newArray[i] = copyArray ((ArrayReference) val);
3029                        else newArray[i] = val;
3030                }
3031                return newArray;
3032        }
3033
3034        private boolean valueHasChanged (Value oldValue, Value newValue) {
3035                if (oldValue == null && newValue == null) return false;
3036                if (oldValue == null && newValue != null) return true;
3037                if (oldValue != null && newValue == null) return true;
3038                if (!oldValue.equals (newValue)) return true;
3039                if (!(oldValue instanceof ArrayReference)) return false;
3040                return arrayValueHasChanged ((ArrayReference) oldValue, (ArrayReference) newValue);
3041        }
3042        private boolean arrayStaticFieldHasChanged (Boolean show, ThreadReference thr, IndentPrinter printer) {
3043                boolean result = false;
3044                boolean print = false;
3045                String debug = Trace.DEBUG ? "#7" : "";
3046                String change = ">";
3047                for (ArrayReference a : staticArrays.keySet ()) {
3048                        Object[] objArray = staticArrays.get (a);
3049                        if (arrayValueHasChangedHelper (objArray, a)) {
3050                                result = true;
3051                                print = true;
3052                        }
3053                        if (show && print) {
3054                                printer.println (thr, "  " + debug + change + " " + staticArrayNames.get (a) + " = " + Format.valueToString (a));
3055                        }
3056                }
3057                return result;
3058        }
3059        private boolean arrayFieldHasChanged (Boolean show, ThreadReference thr, ObjectReference objRef, IndentPrinter printer) {
3060                ReferenceType type = objRef.referenceType (); // get type (class) of object
3061                List<Field> fields; // use allFields() to include inherited fields
3062                try {
3063                        fields = type.fields ();
3064                } catch (ClassNotPreparedException e) {
3065                        throw new Error (Trace.BAD_ERROR_MESSAGE);
3066                }
3067                boolean result = false;
3068                String debug = Trace.DEBUG ? "#2" : "";
3069                String change = ">";
3070                for (Field f : fields) {
3071                        Boolean print = false;
3072                        Value v = objRef.getValue (f);
3073                        if (!(v instanceof ArrayReference)) continue;
3074                        ArrayReference a = (ArrayReference) v;
3075                        if (!arrays.containsKey (a)) {
3076                                registerArray (a);
3077                                change = "|";
3078                                result = true;
3079                                print = true;
3080                        } else {
3081                                Object[] objArray = arrays.get (a);
3082                                if (arrayValueHasChangedHelper (objArray, a)) {
3083                                        result = true;
3084                                        print = true;
3085                                }
3086                        }
3087                        if (show && print) {
3088                                printer.println (thr, "  " + debug + change + " " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f)));
3089                        }
3090                }
3091                return result;
3092        }
3093        private boolean arrayValueHasChanged (ArrayReference oldArray, ArrayReference newArray) {
3094                if (oldArray.length () != newArray.length ()) return true;
3095                int len = oldArray.length ();
3096                if (!arrays.containsKey (newArray)) {
3097                        return true;
3098                }
3099                Object[] oldObjArray = arrays.get (newArray);
3100                //            if (oldObjArray.length != len)
3101                //                throw new Error (Trace.BAD_ERROR_MESSAGE);
3102                return arrayValueHasChangedHelper (oldObjArray, newArray);
3103        }
3104        private boolean arrayValueHasChangedHelper (Object[] oldObjArray, ArrayReference newArray) {
3105                int len = oldObjArray.length;
3106                boolean hasChanged = false;
3107                for (int i = 0; i < len; i++) {
3108                        Object oldObject = oldObjArray[i];
3109                        Value newVal = newArray.getValue (i);
3110                        if (oldObject == null && newVal != null) {
3111                                oldObjArray[i] = newVal;
3112                                hasChanged = true;
3113                        }
3114                        if (oldObject instanceof Value && valueHasChanged ((Value) oldObject, newVal)) {
3115                                //System.out.println ("BOB:" + i + ":" + oldObject + ":" + newVal);
3116                                oldObjArray[i] = newVal;
3117                                hasChanged = true;
3118                        }
3119                        if (oldObject instanceof Object[]) {
3120                                //if (!(newVal instanceof ArrayReference)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3121                                if (arrayValueHasChangedHelper ((Object[]) oldObject, (ArrayReference) newVal)) {
3122                                        hasChanged = true;
3123                                }
3124                        }
3125                }
3126                return hasChanged;
3127        }
3128}
3129
3130/* private static */class Graphviz {
3131        private Graphviz () {} // noninstantiable class
3132        //- This code is based on LJV:
3133        // LJV.java --- Generate a graph of an object, using Graphviz
3134        // The Lightweight Java Visualizer (LJV)
3135        // https://www.cs.auckland.ac.nz/~j-hamer/
3136
3137        //- Author:     John Hamer <J.Hamer@cs.auckland.ac.nz>
3138        //- Created:    Sat May 10 15:27:48 2003
3139        //- Time-stamp: <2004-08-23 12:47:06 jham005>
3140
3141        //- Copyright (C) 2004  John Hamer, University of Auckland
3142        //-
3143        //-   This program is free software; you can redistribute it and/or
3144        //-   modify it under the terms of the GNU General Public License
3145        //-   as published by the Free Software Foundation; either version 2
3146        //-   of the License, or (at your option) any later version.
3147        //-
3148        //-   This program is distributed in the hope that it will be useful,
3149        //-   but WITHOUT ANY WARRANTY; without even the implied warranty of
3150        //-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3151        //-   GNU General Public License for more details.
3152        //-
3153        //-   You should have received a copy of the GNU General Public License along
3154        //-   with this program; if not, write to the Free Software Foundation, Inc.,
3155        //-   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
3156
3157        //- $Id: LJV.java,v 1.1 2004/07/14 02:03:45 jham005 Exp $
3158        //
3159
3160        /**
3161         * Graphics files are saved in directory dirName/mainClassName.
3162         * dirName directory is created if it does not already exist.
3163         * If dirName/mainClassName exists, then numbers are appended to the directory name:
3164         * "dirName/mainClassName 1", "dirName/mainClassName 2", etc.
3165         */
3166        public static void setOutputDirectory (String dirName, String mainClassName) {
3167                if (dirName == null || mainClassName == null) {
3168                        throw new Error ("\n!!!! no nulls please");
3169                }
3170                Graphviz.dirName = dirName;
3171                Graphviz.mainClassName = mainClassName;
3172        }
3173        private static String dirName;
3174        private static String mainClassName;
3175
3176        public static boolean isWindows () {
3177                String osName = System.getProperty("os.name");
3178                if (osName == null) {
3179                        throw new Error("\n!!! os.name not found");
3180                }
3181                osName = osName.toLowerCase(Locale.ENGLISH);
3182                return osName.contains("windows");
3183        }
3184        public static String getDesktop () {
3185                if (isWindows()) {
3186                        // Supposedly this selects the windows desktop folder:
3187                        return javax.swing.filechooser.FileSystemView.getFileSystemView().getHomeDirectory().toString();
3188                } else {
3189                        return System.getProperty ("user.home") + File.separator + "Desktop";
3190                }
3191        }
3192
3193        /**
3194         * The name of the output file is derived from {@code baseFilename} by
3195         * appending successive integers.
3196         */
3197
3198        public static String peekFilename () {
3199                return String.format ("%03d", nextGraphNumber);
3200        }
3201        private static String nextFilename () {
3202                if (baseFilename == null) setBaseFilename ();           
3203                ++nextGraphNumber;
3204                return baseFilename + peekFilename ();          
3205        }
3206        private static int nextGraphNumber = -1;
3207        private static String baseFilename = null;
3208        private static void setBaseFilename () {
3209                if (dirName == null || mainClassName == null) {
3210                        throw new Error ("\n!!!! no call to setOutputDirectory");
3211                }
3212                // create dir
3213                File dir = new File (dirName);
3214                if (!dir.isAbsolute ()) {
3215                        dirName = getDesktop() + File.separator + dirName;
3216                        dir = new File (dirName);
3217                }
3218                if (dir.exists ()) {
3219                        if (!dir.isDirectory ()) 
3220                                throw new Error ("\n!!!! \"" + dir + "\" is not a directory");
3221                        if (!dir.canWrite ()) 
3222                                throw new Error ("\n!!!! Unable to write directory: \"" + dir + "\"");
3223                } else {
3224                        dir.mkdirs ();
3225                }
3226
3227                // create newDir
3228                String prefix = dirName + File.separator;
3229                String[] mainClassPath = mainClassName.split ("\\.");
3230                mainClassName = mainClassPath[mainClassPath.length-1];
3231                File newDir = new File (prefix + mainClassName);
3232                int suffix = 0;
3233                while (newDir.exists()) { 
3234                        suffix++; 
3235                        newDir = new File(prefix + mainClassName + " " + suffix);
3236                }
3237                newDir.mkdir ();
3238
3239                if (!newDir.isDirectory () || !newDir.canWrite ())
3240                        throw new Error ("Failed setOutputDirectory \"" + newDir + "\"");
3241                baseFilename = newDir + File.separator;
3242                nextGraphNumber = -1;
3243        }
3244        //      /** @deprecated */
3245        //      private static void setOutputFilenamePrefix (String s) {
3246        //              File f = new File (s);
3247        //              String fCanonical;
3248        //              try { 
3249        //                      fCanonical = f.getCanonicalPath ();
3250        //              } catch (IOException e) {
3251        //                      throw new Error ("Failed setBaseFilename \"" + f + "\"");
3252        //              }
3253        //
3254        //              String newBaseFilename;
3255        //              if (f.isDirectory ()) {                         
3256        //                      if (f.canWrite ()) {
3257        //                              newBaseFilename = fCanonical + "/trace-"; 
3258        //                      } else {
3259        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3260        //                      }
3261        //              } else {
3262        //                      File parent = (f == null) ? null : f.getParentFile ();
3263        //                      if (parent == null || parent.canWrite ()) {
3264        //                              newBaseFilename = fCanonical;
3265        //                      } else {
3266        //                              System.err.println ("Cannot open directory \"" + f.getParent () + "\" for writing; using the current directory for graphziv output.");
3267        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3268        //                      }
3269        //              }
3270        //              if (!newBaseFilename.equals (baseFilename)) {
3271        //                      baseFilename = newBaseFilename;
3272        //                      nextGraphNumber = -1;
3273        //              }
3274        //      }
3275
3276        public static final HashMap<String, String> objectAttributeMap = new HashMap<> ();
3277        public static final HashMap<String, String> staticClassAttributeMap = new HashMap<> ();
3278        public static final HashMap<String, String> frameAttributeMap = new HashMap<> ();
3279        public static final HashMap<String, String> fieldAttributeMap = new HashMap<> ();
3280
3281        // ----------------------------------- utilities -----------------------------------------------
3282
3283        private static boolean canTreatAsPrimitive (Value v) {
3284                if (v == null || v instanceof PrimitiveValue) return true;
3285                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && v instanceof StringReference) return true;
3286                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (v.type ())) return true;
3287                return false;
3288        }
3289        private static boolean looksLikePrimitiveArray (ArrayReference obj) {
3290                try {
3291                        if (((ArrayType) obj.type ()).componentType () instanceof PrimitiveType) return true;
3292                } catch (ClassNotLoadedException e) {
3293                        return false;
3294                }
3295
3296                for (int i = 0, len = obj.length (); i < len; i++)
3297                        if (!canTreatAsPrimitive (obj.getValue (i))) return false;
3298                return true;
3299        }
3300        private static boolean canIgnoreObjectField (Field field) {
3301                if (!Format.isObjectField (field)) return true;
3302                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3303                        if (ignoredField.equals (field.name ())) return true;
3304                return false;
3305        }
3306        private static boolean canIgnoreStaticField (Field field) {
3307                if (!Format.isStaticField (field)) return true;
3308                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3309                        if (ignoredField.equals (field.name ())) return true;
3310                return false;
3311        }
3312
3313        //private static final String canAppearUnquotedInLabelChars = " /$&*@#!-+()^%;_[],;.=";
3314        private static boolean canAppearUnquotedInLabel (char c) {
3315                return true;
3316                //return canAppearUnquotedInLabelChars.indexOf (c) != -1 || Character.isLetter (c) || Character.isDigit (c);
3317        }
3318        private static final String quotable = "\\\"<>{}|";
3319        protected static String quote (String s) {
3320                s = unescapeJavaString (s);
3321                StringBuffer sb = new StringBuffer ();
3322                for (int i = 0, n = s.length (); i < n; i++) {
3323                        char c = s.charAt (i);
3324                        if (quotable.indexOf (c) != -1) sb.append ('\\').append (c);
3325                        else if (canAppearUnquotedInLabel (c)) sb.append (c);
3326                        else sb.append ("\\\\u").append (Integer.toHexString (c));
3327                }
3328                return sb.toString ();
3329        }
3330        /**
3331         * Unescapes a string that contains standard Java escape sequences.
3332         * <ul>
3333         * <li><strong>\\b \\f \\n \\r \\t \\" \\'</strong> :
3334         * BS, FF, NL, CR, TAB, double and single quote.</li>
3335         * <li><strong>\\N \\NN \\NNN</strong> : Octal character
3336         * specification (0 - 377, 0x00 - 0xFF).</li>
3337         * <li><strong>\\uNNNN</strong> : Hexadecimal based Unicode character.</li>
3338         * </ul>
3339         *
3340         * @param st
3341         *            A string optionally containing standard java escape sequences.
3342         * @return The translated string.
3343         */
3344        // from http://udojava.com/2013/09/28/unescape-a-string-that-contains-standard-java-escape-sequences/
3345        private static String unescapeJavaString(String st) {
3346                StringBuilder sb = new StringBuilder(st.length());
3347                for (int i = 0; i < st.length(); i++) {
3348                        char ch = st.charAt(i);
3349                        if (ch == '\\') {
3350                                char nextChar = (i == st.length() - 1) ? '\\' : st.charAt(i + 1);
3351                                // Octal escape?
3352                                if (nextChar >= '0' && nextChar <= '7') {
3353                                        String code = "" + nextChar;
3354                                        i++;
3355                                        if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3356                                                code += st.charAt(i + 1);
3357                                                i++;
3358                                                if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3359                                                        code += st.charAt(i + 1);
3360                                                        i++;
3361                                                }
3362                                        }
3363                                        sb.append((char) Integer.parseInt(code, 8));
3364                                        continue;
3365                                }
3366                                switch (nextChar) {
3367                                case '\\': ch = '\\'; break;
3368                                case 'b': ch = '\b'; break;
3369                                case 'f': ch = '\f'; break;
3370                                case 'n': ch = '\n'; break;
3371                                case 'r': ch = '\r'; break;
3372                                case 't': ch = '\t'; break;
3373                                case '\"': ch = '\"'; break;
3374                                case '\'': ch = '\''; break;
3375                                // Hex Unicode: u????
3376                                case 'u':
3377                                        if (i >= st.length() - 5) { ch = 'u'; break; }
3378                                        int code = Integer.parseInt(st.substring (i+2,i+6), 16);
3379                                        sb.append(Character.toChars(code));
3380                                        i += 5;
3381                                        continue;
3382                                }
3383                                i++;
3384                        }
3385                        sb.append(ch);
3386                }
3387                return sb.toString();
3388        }
3389
3390
3391
3392        // ----------------------------------- values -----------------------------------------------
3393
3394        protected static final String PREFIX_UNUSED_LABEL = "_____";
3395        private static final String PREFIX_LABEL = "L";
3396        private static final String PREFIX_ARRAY = "A";
3397        private static final String PREFIX_OBJECT = "N";
3398        private static final String PREFIX_STATIC = "S";
3399        private static final String PREFIX_FRAME = "F";
3400        private static final String PREFIX_RETURN = "returnValue";
3401        private static final String PREFIX_EXCEPTION = "exception";
3402
3403        private static void processPrimitiveArray (ArrayReference obj, PrintWriter out) {
3404                out.print (objectGvName (obj) + "[label=\"");
3405                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3406                for (int i = 0, len = obj.length (); i < len; i++) {
3407                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3408                        Value v = obj.getValue (i);
3409                        if (v != null) processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, "", v, out);
3410                }
3411                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3412        }
3413        private static void processObjectArray (ArrayReference obj, PrintWriter out, Set<ObjectReference> visited) {
3414                out.print (objectGvName (obj) + "[label=\"");
3415                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3416                int len = obj.length ();
3417                for (int i = 0; i < len; i++) {
3418                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3419                        out.print ("<" + PREFIX_ARRAY + i + ">");
3420                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) {
3421                                ObjectReference ref = (ObjectReference) obj.getValue (i);
3422                                out.print (objectNameOnly (ref));
3423                        }
3424                }
3425                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3426                for (int i = 0; i < len; i++) {
3427                        ObjectReference ref = (ObjectReference) obj.getValue (i);
3428                        if (ref == null) continue;
3429                        out.println (objectGvName (obj) + ":" + PREFIX_ARRAY + i + ":c -> " + objectGvName (ref) + "[label=\"" + i + "\"" + Trace.GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES + "];");
3430                        processObject (ref, out, visited);
3431                }
3432        }
3433        private static void processValueStandalone (String gvSource, String arrowAttributes, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3434                if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3435                ObjectReference objRef = (ObjectReference) val;
3436                String GvName = objectGvName (objRef);
3437                if (isNode (objRef.type())) {
3438                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3439                }
3440                out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3441                processObject (objRef, out, visited);
3442        }
3443        private static boolean processValueInline (boolean showNull, String prefix, Value val, PrintWriter out) {
3444                if ((!Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) && (!canTreatAsPrimitive (val))) return false;
3445                if (val == null && !showNull)
3446                        return false;
3447                out.print (prefix);
3448                if (val == null) {
3449                        out.print (quote ("null"));
3450                } else if (val instanceof PrimitiveValue) {
3451                        out.print (quote (val.toString ()));
3452                } else if (Trace.SHOW_STRINGS_AS_PRIMITIVE && val instanceof StringReference) {
3453                        out.print (quote (val.toString ()));
3454                } else if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (val.type ())) {
3455                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3456                } else if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY && val instanceof ObjectReference) {
3457                        out.print (quote (objectNameOnly (val)));
3458                }
3459                return true;
3460        }
3461        // val must be primitive, wrapper or string
3462        private static void processWrapperAsSimple (String gvName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3463                String cabs = null;
3464                out.print (gvName + "[label=\"");
3465                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS && val instanceof ObjectReference) {
3466                        out.print (objectNameOnly (val) + " : ");
3467                }
3468                if (val instanceof PrimitiveValue) {
3469                        out.print (quote (val.toString ()));
3470                } else if (val instanceof StringReference) {
3471                        out.print (quote (val.toString ()));
3472                } else {
3473                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3474                }
3475                out.println ("\"" + Trace.GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3476        }
3477
3478        // ----------------------------------- objects -----------------------------------------------
3479
3480        private static String objectNameOnly (Value val) {
3481                if (val == null) return "null";
3482                if (!(val instanceof ObjectReference)) return ""; 
3483                return "@" + ((ObjectReference)val).uniqueID ();
3484        }
3485        private static String objectName (ObjectReference obj) {
3486                if (obj == null) return "";
3487                String objString = (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) ? "@" + obj.uniqueID () + " : " : "";
3488                return objString + Format.shortenFullyQualifiedName (obj.type ().name ());
3489        }
3490        private static String objectGvName (ObjectReference obj) {
3491                return PREFIX_OBJECT + obj.uniqueID ();
3492        }
3493        private static boolean objectHasPrimitives (List<Field> fs, ObjectReference obj) {
3494                for (Field f : fs) {
3495                        if (canIgnoreObjectField (f)) continue;
3496                        if (canTreatAsPrimitive (obj.getValue (f))) return true;
3497                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3498                }
3499                return false;
3500        }
3501        private static boolean objectHasNonNodeReferences (List<Field> fs, ObjectReference obj) {
3502                for (Field f : fs) {
3503                        if (isNode (f)) continue;
3504                        if (canIgnoreObjectField (f)) continue;
3505                        if (canTreatAsPrimitive (obj.getValue (f))) continue;
3506                        return true;
3507                }
3508                return false;
3509        }
3510        private static void labelObjectWithNoPrimitiveFields (ObjectReference obj, PrintWriter out) {
3511                String cabs = objectAttributeMap.get (obj.type ().name ());
3512                out.println (objectGvName (obj) + "[label=\"" + objectName (obj) + "\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3513        }
3514        private static void labelObjectWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3515                out.print (objectGvName (obj) + "[label=\"" + objectName (obj) + "|{");
3516                String sep = "";
3517                for (Field f : fs) {
3518                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? f.name () + " = " : "";
3519                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3520                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = "|";
3521                        } 
3522                }
3523                String cabs = objectAttributeMap.get (obj.type ().name ());
3524                out.println ("}\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3525        }
3526        private static void labelNodeWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3527                out.print (objectGvName (obj) + "[label=\"");
3528                String sep = "";
3529                for (Field f : fs) {
3530                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_LABELS ? f.name () + " = " : "";
3531                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3532                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = ", ";
3533                        }
3534                }
3535                String cabs = objectAttributeMap.get (obj.type ().name ());
3536                out.println ("\"" + Trace.GRAPHVIZ_NODE_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3537        }
3538        private static int nullId = 0;
3539        private static void processNodeStandalone (boolean srcIsNode, boolean srcHasNonNodeReferences, String gvSource, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3540                String arrowAttributes = Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES;
3541                if (!srcIsNode) {
3542                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3543                }
3544                if (srcIsNode && !srcHasNonNodeReferences && !Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_ARROWS) {
3545                        fieldName = "";
3546                }
3547                if (val == null) {
3548                        String id = "null__" + nullId;
3549                        nullId++;
3550                        out.print (id + "[shape=\"point\"];");                  
3551                        out.println (gvSource + " -> " + id + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3552                } else {
3553                        if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3554                        ObjectReference objRef = (ObjectReference) val;
3555                        String GvName = objectGvName (objRef);
3556                        out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3557                        processObject (objRef, out, visited);
3558                }
3559        }
3560        private static void processObjectWithLabel (String label, ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3561                processObject (obj, out, visited);
3562                if (!label.startsWith (PREFIX_UNUSED_LABEL)) {
3563                        String gvObjName = objectGvName (obj);
3564                        String gvLabelName = PREFIX_LABEL + label;
3565                        out.println (gvLabelName + "[label=\"" + label + "\"" + Trace.GRAPHVIZ_LABEL_BOX_ATTRIBUTES + "];");
3566                        out.println (gvLabelName + " -> " + gvObjName + "[label=\"\"" + Trace.GRAPHVIZ_LABEL_ARROW_ATTRIBUTES + "];");
3567                }
3568        }
3569        private static Value valueByFieldname (ObjectReference obj, String fieldName) {
3570                ReferenceType type = (ReferenceType) obj.type ();
3571                Field field = type.fieldByName (fieldName);
3572                return obj.getValue (field);
3573        }
3574        private static boolean isNode (Type type) {
3575                String typeName = type.name ();
3576                for (String s : Trace.GRAPHVIZ_NODE_CLASS) {
3577                        //System.err.printf ("s=%s, typeName=%s\n", s, typeName);
3578                        if (typeName.endsWith (s)) return true;
3579                }
3580                return false;
3581        }
3582        private static boolean isNode (Field f) {
3583                try {
3584                        return isNode (f.type());
3585                } catch (ClassNotLoadedException e) {
3586                        return false; //throw new Error ("\n!!!! Node class not loaded");
3587                }
3588        }
3589        private static void processObject (ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3590                if (visited.add (obj)) {
3591                        Type type = obj.type ();
3592                        String typeName = type.name ();
3593
3594                        if (!Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && Format.isWrapper (type)) {
3595                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3596                        } else if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && obj instanceof StringReference) {
3597                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3598                        } else if (obj instanceof ArrayReference) {
3599                                ArrayReference arr = (ArrayReference) obj;
3600                                if (looksLikePrimitiveArray (arr)) processPrimitiveArray (arr, out);
3601                                else processObjectArray (arr, out, visited);
3602                        } else {
3603                                List<Field> fs = ((ReferenceType) type).fields ();
3604                                if (isNode (type)) labelNodeWithSomePrimitiveFields (obj, fs, out);
3605                                else if (objectHasPrimitives (fs, obj)) labelObjectWithSomePrimitiveFields (obj, fs, out);
3606                                else labelObjectWithNoPrimitiveFields (obj, out);
3607                                if (!Format.matchesExcludePrefixShow (typeName)) {
3608                                        //System.err.println (typeName);
3609                                        String source = objectGvName (obj);
3610                                        for (Field f : fs) {
3611                                                Value value = obj.getValue (f);
3612                                                if (isNode (f)) {
3613                                                        processNodeStandalone (isNode (type), objectHasNonNodeReferences (fs, obj), source, f.name (), value, out, visited);
3614                                                } else if ((!canIgnoreObjectField (f)) && (!canTreatAsPrimitive (value))) {
3615                                                        processValueStandalone (source, Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES, f.name (), value, out, visited);
3616                                                }
3617                                        }
3618                                }
3619                        }
3620                }
3621        }
3622
3623        // ----------------------------------- static classes -----------------------------------------------
3624
3625        private static String staticClassName (ReferenceType type) {
3626                return Format.shortenFullyQualifiedName (type.name ());
3627        }
3628        private static String staticClassGvName (ReferenceType type) {
3629                return PREFIX_STATIC + type.classObject ().uniqueID ();
3630        }
3631        private static boolean staticClassHasFields (List<Field> fs) {
3632                for (Field f : fs) {
3633                        if (!canIgnoreStaticField (f)) return true;
3634                }
3635                return false;
3636        }
3637        private static boolean staticClassHasPrimitives (List<Field> fs, ReferenceType staticClass) {
3638                for (Field f : fs) {
3639                        if (canIgnoreStaticField (f)) continue;
3640                        if (canTreatAsPrimitive (staticClass.getValue (f))) return true;
3641                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3642                }
3643                return false;
3644        }
3645        private static void labelStaticClassWithNoPrimitiveFields (ReferenceType type, PrintWriter out) {
3646                String cabs = staticClassAttributeMap.get (type.name ());
3647                out.println (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3648        }
3649        private static void labelStaticClassWithSomePrimitiveFields (ReferenceType type, List<Field> fs, PrintWriter out) {
3650                out.print (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "|{");
3651                String sep = "";
3652                for (Field field : fs) {
3653                        if (!canIgnoreStaticField (field)) {
3654                                String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? field.name () + " = " : "";
3655                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, type.getValue (field), out)) sep = "|";
3656                        }
3657                }
3658                String cabs = staticClassAttributeMap.get (type.name ());
3659                out.println ("}\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3660        }
3661        private static void processStaticClass (ReferenceType type, PrintWriter out, Set<ObjectReference> visited) {
3662                String typeName = type.name ();
3663                List<Field> fs = type.fields ();
3664                if (!staticClassHasFields (fs)) {
3665                        return;
3666                }
3667                if (staticClassHasPrimitives (fs, type)) {
3668                        labelStaticClassWithSomePrimitiveFields (type, fs, out);
3669                } else {
3670                        labelStaticClassWithNoPrimitiveFields (type, out);
3671                }
3672                if (!Format.matchesExcludePrefixShow (type.name ())) {
3673                        String source = staticClassGvName (type);
3674                        for (Field f : fs) {
3675                                if (f.isStatic ()) {
3676                                        Value value = type.getValue (f);
3677                                        if ((!canIgnoreStaticField (f)) && (!canTreatAsPrimitive (value))) {
3678                                                String name = f.name ();
3679                                                processValueStandalone (source, Trace.GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES, name, value, out, visited);
3680                                        }
3681                                }
3682                        }
3683                }
3684        }
3685
3686        // ----------------------------------- frames -----------------------------------------------
3687        private static String frameName (int frameNumber, StackFrame frame, Method method, int lineNumber) {
3688                String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + frameNumber + " : " : "";
3689                return objString + Format.methodToString (method, true, false, ".") + " # " + lineNumber;
3690        }
3691        private static String frameGvName (int frameNumber) {
3692                return PREFIX_FRAME + frameNumber;
3693        }
3694        private static boolean frameHasPrimitives (Map<LocalVariable, Value> ls) {
3695                for (LocalVariable lv : ls.keySet ()) {
3696                        Value v = ls.get (lv);
3697                        if (canTreatAsPrimitive (v)) return true;
3698                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3699                }
3700                return false;
3701        }
3702        private static void labelFrameWithNoPrimitiveLocals (int frameNumber, StackFrame frame, PrintWriter out) {
3703                Location location = frame.location ();
3704                ReferenceType type = location.declaringType ();
3705                Method method = location.method ();
3706                String attributes = frameAttributeMap.get (type.name ());
3707                out.println (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES
3708                                + (attributes == null ? "" : "," + attributes) + "];");
3709        }
3710        private static void labelFrameWithSomePrimitiveLocals (int frameNumber, StackFrame frame, Map<LocalVariable, Value> ls, PrintWriter out) {
3711                Location location = frame.location ();
3712                ReferenceType type = location.declaringType ();
3713                Method method = location.method ();
3714                out.print (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "|{");
3715                String sep = "";
3716                for (LocalVariable lv : ls.keySet ()) {
3717                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? lv.name () + " = " : "";
3718                        if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_VARIABLES, sep + name, ls.get (lv), out)) sep = "|";
3719                }
3720                String cabs = frameAttributeMap.get (type.name ());
3721                out.println ("}\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3722        }
3723        private static boolean processFrame (int frameNumber, StackFrame frame, PrintWriter out, Set<ObjectReference> visited) {
3724                Location location = frame.location ();
3725                ReferenceType type = location.declaringType ();
3726                Method method = location.method ();
3727                if (Format.matchesExcludePrefixShow (type.name ())) return false;
3728
3729                Map<LocalVariable, Value> ls;
3730                try {
3731                        ls = frame.getValues (frame.visibleVariables ());
3732                } catch (AbsentInformationException e) {
3733                        return false;
3734                }
3735                if (frameHasPrimitives (ls)) {
3736                        labelFrameWithSomePrimitiveLocals (frameNumber, frame, ls, out);
3737                } else {
3738                        labelFrameWithNoPrimitiveLocals (frameNumber, frame, out);
3739                }
3740                ObjectReference thisObject = frame.thisObject ();
3741                if (thisObject != null) processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "this", thisObject, out, visited);
3742                for (LocalVariable lv : ls.keySet ()) {
3743                        Value value = ls.get (lv);
3744                        if (!canTreatAsPrimitive (value)) {
3745                                processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, lv.name (), value, out, visited);
3746                        }
3747                }
3748                return true;
3749        }
3750
3751        // ----------------------------------- top level -----------------------------------------------
3752
3753        public static void drawFramesCheck (String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses) {
3754                if (Trace.drawStepsOfInternal (frames, returnVal)) {
3755                        int len = 0;
3756                        if (frames!=null) len = (Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME)? 2 : frames.size();
3757                        drawFrames (0, len, loc, returnVal, exnVal, frames, staticClasses, false);
3758                }
3759        }
3760        public static void drawFrames (int start, int len, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, boolean overrideShowStatics) {
3761                drawStuff (loc, (out) -> {
3762                        Set<ObjectReference> visited = new HashSet<> ();
3763                        if ((overrideShowStatics || Trace.GRAPHVIZ_SHOW_STATIC_CLASSES) && staticClasses != null) {
3764                                for (ReferenceType staticClass : staticClasses) {
3765                                        processStaticClass (staticClass, out, visited);
3766                                }
3767                        }
3768                        if (frames != null) {
3769                                for (int i = len - 1, prev = i; i >= start; i--) {
3770                                        StackFrame currentFrame = frames.get (i);
3771                                        Method meth = currentFrame.location ().method ();
3772                                        if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) continue;
3773                                        if (processFrame (len - i, currentFrame, out, visited)) {
3774                                                if (prev != i) {
3775                                                        out.println (frameGvName (len - i) + " -> " + frameGvName (len - prev) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3776                                                        prev = i;
3777                                                }
3778                                        }
3779                                }
3780                                // show the return value -- without this, it mysteriously disappears when drawing all steps
3781                                if (!Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME && returnVal != null && !(returnVal instanceof VoidValue)) {
3782                                        String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + (len + 1) + " : " : "";
3783                                        if (canTreatAsPrimitive (returnVal)) {
3784                                                out.print (PREFIX_RETURN + " [label=\"" + objString + "returnValue = ");
3785                                                processValueInline (true, "", returnVal, out);
3786                                                out.println ("\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3787                                                out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3788                                        } else {
3789                                                out.println (PREFIX_RETURN + " [label=\"" + objString + "returnValue\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3790                                                processValueStandalone (PREFIX_RETURN, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", returnVal, out, visited);
3791                                                out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3792                                        }
3793                                }
3794                        }
3795                        // show the exception value
3796                        if (exnVal != null && !(exnVal instanceof VoidValue)) {
3797                                if (canTreatAsPrimitive (exnVal)) {
3798                                        out.print (PREFIX_EXCEPTION + " [label=\"exception = ");
3799                                        processValueInline (true, "", exnVal, out);
3800                                        out.println ("\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3801                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3802                                } else {
3803                                        out.println (PREFIX_EXCEPTION + " [label=\"exception\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3804                                        processValueStandalone (PREFIX_EXCEPTION, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", exnVal, out, visited);
3805                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3806                                }
3807                        }
3808                });
3809        }
3810        public static void drawObjects (String loc, Map<String, ObjectReference> objects) {
3811                drawStuff (loc, (out) -> {
3812                        Set<ObjectReference> visited = new HashSet<> ();
3813                        for (String key : objects.keySet ()) {
3814                                processObjectWithLabel (key, objects.get (key), out, visited);
3815                        }
3816                });
3817        }
3818        protected static void drawStuff (String loc, Consumer<PrintWriter> consumer) {
3819                String filenamePrefix = nextFilename ();
3820                String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3821                File gvFile = new File (filenamePrefix + theLoc + ".gv");
3822                PrintWriter out;
3823                try {
3824                        out = new PrintWriter (new FileWriter (gvFile));
3825                } catch (IOException e) {
3826                        throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3827                }
3828                out.println ("digraph Java {");
3829                consumer.accept (out);
3830                out.println ("}");
3831                out.close ();
3832                //System.err.println (gvFile);
3833                if (Trace.GRAPHVIZ_RUN_GRAPHVIZ) {
3834                        String executable = null;
3835                        for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3836                                if (new File (s).canExecute ()) executable = s;
3837                        }
3838                        if (executable != null) {
3839                                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_OUTPUT_FORMAT);
3840                                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_OUTPUT_FORMAT);
3841                                pb.redirectInput (gvFile);
3842                                pb.redirectOutput (outFile);
3843                                int result = -1;
3844                                try {
3845                                        result = pb.start ().waitFor ();
3846                                } catch (IOException e) {
3847                                        throw new Error ("\n!!!! Cannot execute " + executable + "\n!!!! Make sure you have installed http://www.graphviz.org/"
3848                                                        + "\n!!!! Check the value of GRAPHVIZ_POSSIBLE_DOT_LOCATIONS in " + Trace.class.getCanonicalName ());
3849                                } catch (InterruptedException e) {
3850                                        throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3851                                }
3852                                if (result == 0) {
3853                                        if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3854                                                gvFile.delete ();
3855                                        }
3856                                } else {
3857                                        outFile.delete ();
3858                                }
3859                        }
3860                }
3861        }
3862
3863        //    public static void drawFrames (int start, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, Map<String, ObjectReference> objects) {
3864        //        String filenamePrefix = nextFilename ();
3865        //        String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3866        //        File gvFile = new File (filenamePrefix + theLoc + ".gv");
3867        //        PrintWriter out;
3868        //        try {
3869        //            out = new PrintWriter (new FileWriter (gvFile));
3870        //        } catch (IOException e) {
3871        //            throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3872        //        }
3873        //        processFrames (start, returnVal, exnVal, frames, staticClasses, objects, out);
3874        //        out.close ();
3875        //        //System.err.println (gvFile);
3876        //        if (Trace.GRAPHVIZ_RUN_DOT) {
3877        //            String executable = null;
3878        //            for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3879        //                if (new File (s).canExecute ())
3880        //                    executable = s;
3881        //            }
3882        //            if (executable != null) {
3883        //                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3884        //                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3885        //                pb.redirectInput (gvFile);
3886        //                pb.redirectOutput(outFile);
3887        //                int result = -1;
3888        //                try {
3889        //                    result = pb.start ().waitFor ();
3890        //                } catch (IOException e) {
3891        //                    throw new Error ("\n!!!! Cannot execute " + executable +
3892        //                            "\n!!!! Make sure you have installed http://www.graphviz.org/" +
3893        //                            "\n!!!! Check the value of GRAPHVIZ_DOT_COMMAND in " + Trace.class.getCanonicalName ());
3894        //                } catch (InterruptedException e) {
3895        //                    throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3896        //                }
3897        //                if (result == 0) {
3898        //                    if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3899        //                        gvFile.delete ();
3900        //                    }
3901        //                } else {
3902        //                    outFile.delete ();
3903        //                }
3904        //            }
3905        //        }
3906        //    }
3907}