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