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                        conn = Bootstrap.virtualMachineManager ().defaultConnector(); //"com.sun.jdi.CommandLineLaunch"
1415                        try {
1416                                connArgs = conn.defaultArguments ();
1417                                connArgs.get ("main").setValue (mainClassName);
1418                                connArgs.get ("options").setValue (argsString);
1419                                StdOut.println(connArgs);
1420                                //TODO print default arguments (it's a map) and find the classpath
1421                        } catch (NullPointerException e) {
1422                                throw new Error ("\n!!!! Bad launching connector");
1423                        }
1424
1425                } else {
1426                        // The default launcher works fine for windows, which uses shared memory
1427                        // for communication between the processes.
1428                        // The default launcher fails on unix-like systems, which uses sockets
1429                        // for communication between the processes.
1430                        // In particular, the communication is set up using hostname; this causes 
1431                        // communication to fail when the hostname is not routable in DNS or /etc/hosts
1432                        // The problem is in the file com/sun/tools/jdiSocketTransportService$SocketListenKey.java
1433                        // To fix, you just need to comment out the try block with "address = InetAddress.getLocalHost ();"
1434                        // As of Java 9, this fix is not available to outsiders, due to the module system
1435                        //                      
1436                        // The use of the raw launcher here is reverse engineered from 
1437                        //  com.sun.tools.jdi.SunCommandLineLauncher
1438                        //  com.sun.tools.jdi.RawCommandLineLauncher
1439                        //
1440                        // The raw connector has these attributes:
1441                        //   command = "" : Raw command to start the debugged application VM
1442                        //   address = "" : Address from which to listen for a connection after the raw command is run
1443                        //   quote = "\"" : Character used to combine space-delimited text into a single command line argument
1444                        //
1445                        // The default connector has these attributes:
1446                        //   home = System.getProperty("java.home") : Home directory of the SDK or runtime environment used to launch the application
1447                        //   options = ""    : Launched VM options
1448                        //   main = ""       : Main class and arguments, or if -jar is an option, the main jar file and arguments
1449                        //   suspend = true  : All threads will be suspended before execution of main
1450                        //   quote = "\""    : Character used to combine space-delimited text into a single command line argument
1451                        //   vmexec = "java" : Name of the Java VM launcher
1452                        String fs = System.getProperty ("file.separator");
1453                        String command = 
1454                                        "\"" + System.getProperty ("java.home") + fs + "bin" + fs + "java" + "\" " +
1455                                                        "-Xdebug " +
1456                                                        "-Xrunjdwp:transport=dt_socket,address=127.0.0.1:8900,suspend=y " + 
1457                                                        argsString +
1458                                                        " " +
1459                                                        mainClassName +
1460                                                        "";
1461                        //System.out.println (command);
1462                        for (LaunchingConnector lc : Bootstrap.virtualMachineManager ().launchingConnectors ()) {
1463                                if (lc.name ().equals ("com.sun.jdi.RawCommandLineLaunch")) conn = lc;
1464                        }
1465                        try {
1466                                connArgs = conn.defaultArguments ();
1467                                connArgs.get ("command").setValue (command);
1468                                connArgs.get ("address").setValue ("127.0.0.1:8900");
1469                        } catch (NullPointerException e) {
1470                                throw new Error ("\n!!!! Bad launching connector");
1471                        }
1472                }
1473
1474                VirtualMachine vm = null;
1475                try {
1476                        vm = conn.launch (connArgs); // launch the JVM and connect to it
1477                } catch (IOException e) {
1478                        throw new Error ("\n!!!! Unable to launch JVM: " + e);
1479                } catch (IllegalConnectorArgumentsException e) {
1480                        throw new Error ("\n!!!! Internal error: " + e);
1481                } catch (VMStartException e) {
1482                        throw new Error ("\n!!!! JVM failed to start: " + e.getMessage ());
1483                }
1484
1485                return vm;
1486        }
1487
1488        // monitor the JVM running the application
1489        private static void monitorJVM (VirtualMachine vm) {
1490                // start JDI event handler which displays trace info
1491                JDIEventMonitor watcher = new JDIEventMonitor (vm);
1492                watcher.start ();
1493
1494                // redirect VM's output and error streams to the system output and error streams
1495                Process process = vm.process ();
1496                Thread errRedirect = new StreamRedirecter ("error reader", process.getErrorStream (), System.err);
1497                Thread outRedirect = new StreamRedirecter ("output reader", process.getInputStream (), System.out);
1498                errRedirect.start ();
1499                outRedirect.start ();
1500
1501                vm.resume (); // start the application
1502
1503                try {
1504                        watcher.join (); // Wait. Shutdown begins when the JDI watcher terminates
1505                        errRedirect.join (); // make sure all the stream outputs have been forwarded before we exit
1506                        outRedirect.join ();
1507                } catch (InterruptedException e) {}
1508        }
1509}
1510
1511/**
1512 * StreamRedirecter is a thread which copies it's input to it's output and
1513 * terminates when it completes.
1514 *
1515 * @author Robert Field, September 2005
1516 * @author Andrew Davison, March 2009, ad@fivedots.coe.psu.ac.th
1517 */
1518/* private static */class StreamRedirecter extends Thread {
1519        private static final int BUFFER_SIZE = 2048;
1520        private final Reader in;
1521        private final Writer out;
1522
1523        public StreamRedirecter (String name, InputStream in, OutputStream out) {
1524                super (name);
1525                this.in = new InputStreamReader (in); // stream to copy from
1526                this.out = new OutputStreamWriter (out); // stream to copy to
1527                setPriority (Thread.MAX_PRIORITY - 1);
1528        }
1529
1530        // copy BUFFER_SIZE chars at a time
1531        public void run () {
1532                try {
1533                        char[] cbuf = new char[BUFFER_SIZE];
1534                        int count;
1535                        while ((count = in.read (cbuf, 0, BUFFER_SIZE)) >= 0)
1536                                out.write (cbuf, 0, count);
1537                        out.flush ();
1538                } catch (IOException e) {
1539                        System.err.println ("StreamRedirecter: " + e);
1540                }
1541        }
1542
1543}
1544
1545/**
1546 * Monitor incoming JDI events for a program running in the JVM and print out
1547 * trace/debugging information.
1548 *
1549 * This is a simplified version of EventThread.java from the Trace.java example
1550 * in the demo/jpda/examples.jar file in the JDK.
1551 *
1552 * Andrew Davison: The main addition is the use of the ShowCodes and ShowLines
1553 * classes to list the line being currently executed.
1554 *
1555 * James Riely: See comments in class Trace.
1556 *
1557 * @author Robert Field and Minoru Terada, September 2005
1558 * @author Iman_S, June 2008
1559 * @author Andrew Davison, ad@fivedots.coe.psu.ac.th, March 2009
1560 * @author James Riely, jriely@cs.depaul.edu, August 2014
1561 */
1562/* private static */class JDIEventMonitor extends Thread {
1563        // exclude events generated for these classes
1564        private final VirtualMachine vm; // the JVM
1565        private boolean connected = true; // connected to VM?
1566        private boolean vmDied; // has VM death occurred?
1567        private final JDIEventHandler printer = new Printer ();
1568
1569        public JDIEventMonitor (VirtualMachine jvm) {
1570                super ("JDIEventMonitor");
1571                vm = jvm;
1572                setEventRequests ();
1573        }
1574
1575        // Create and enable the event requests for the events we want to monitor in
1576        // the running program.
1577        //
1578        // Created here:
1579        //
1580        //    createThreadStartRequest()
1581        //    createThreadDeathRequest()
1582        //    createClassPrepareRequest()
1583        //    createClassUnloadRequest()
1584        //    createMethodEntryRequest()
1585        //    createMethodExitRequest()
1586        //    createExceptionRequest(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught)
1587        //    createMonitorContendedEnterRequest()
1588        //    createMonitorContendedEnteredRequest()
1589        //    createMonitorWaitRequest()
1590        //    createMonitorWaitedRequest()
1591        //
1592        // Created when class is loaded:
1593        //
1594        //    createModificationWatchpointRequest(Field field)
1595        //
1596        // Created when thread is started:
1597        //
1598        //    createStepRequest(ThreadReference thread, int size, int depth)
1599        //
1600        // Unused:
1601        //
1602        //    createAccessWatchpointRequest(Field field)
1603        //    createBreakpointRequest(Location location)
1604        //
1605        // Unnecessary:
1606        //
1607        //    createVMDeathRequest() // these happen even without being requested
1608        //
1609        private void setEventRequests () {
1610                EventRequestManager mgr = vm.eventRequestManager ();
1611                {
1612                        ThreadStartRequest x = mgr.createThreadStartRequest (); // report thread starts
1613                        x.enable ();
1614                }
1615                {
1616                        ThreadDeathRequest x = mgr.createThreadDeathRequest (); // report thread deaths
1617                        x.enable ();
1618                }
1619                {
1620                        ClassPrepareRequest x = mgr.createClassPrepareRequest (); // report class loads
1621                        for (String s : Trace.EXCLUDE_GLOBS)
1622                                x.addClassExclusionFilter (s);
1623                        // x.setSuspendPolicy(EventRequest.SUSPEND_ALL);
1624                        x.enable ();
1625                }
1626                {
1627                        ClassUnloadRequest x = mgr.createClassUnloadRequest (); // report class unloads
1628                        for (String s : Trace.EXCLUDE_GLOBS)
1629                                x.addClassExclusionFilter (s);
1630                        // x.setSuspendPolicy(EventRequest.SUSPEND_ALL);
1631                        x.enable ();
1632                }
1633                {
1634                        MethodEntryRequest x = mgr.createMethodEntryRequest (); // report method entries
1635                        for (String s : Trace.EXCLUDE_GLOBS)
1636                                x.addClassExclusionFilter (s);
1637                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1638                        x.enable ();
1639                }
1640                {
1641                        MethodExitRequest x = mgr.createMethodExitRequest (); // report method exits
1642                        for (String s : Trace.EXCLUDE_GLOBS)
1643                                x.addClassExclusionFilter (s);
1644                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1645                        x.enable ();
1646                }
1647                {
1648                        ExceptionRequest x = mgr.createExceptionRequest (null, true, true); // report all exceptions, caught and uncaught
1649                        for (String s : Trace.EXCLUDE_GLOBS)
1650                                x.addClassExclusionFilter (s);
1651                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1652                        x.enable ();
1653                }
1654                {
1655                        MonitorContendedEnterRequest x = mgr.createMonitorContendedEnterRequest ();
1656                        for (String s : Trace.EXCLUDE_GLOBS)
1657                                x.addClassExclusionFilter (s);
1658                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1659                        x.enable ();
1660                }
1661                {
1662                        MonitorContendedEnteredRequest x = mgr.createMonitorContendedEnteredRequest ();
1663                        for (String s : Trace.EXCLUDE_GLOBS)
1664                                x.addClassExclusionFilter (s);
1665                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1666                        x.enable ();
1667                }
1668                {
1669                        MonitorWaitRequest x = mgr.createMonitorWaitRequest ();
1670                        for (String s : Trace.EXCLUDE_GLOBS)
1671                                x.addClassExclusionFilter (s);
1672                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1673                        x.enable ();
1674                }
1675                {
1676                        MonitorWaitedRequest x = mgr.createMonitorWaitedRequest ();
1677                        for (String s : Trace.EXCLUDE_GLOBS)
1678                                x.addClassExclusionFilter (s);
1679                        x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1680                        x.enable ();
1681                }
1682        }
1683
1684        // process JDI events as they arrive on the event queue
1685        public void run () {
1686                EventQueue queue = vm.eventQueue ();
1687                while (connected) {
1688                        try {
1689                                EventSet eventSet = queue.remove ();
1690                                for (Event event : eventSet)
1691                                        handleEvent (event);
1692                                eventSet.resume ();
1693                        } catch (InterruptedException e) {
1694                                // Ignore
1695                        } catch (VMDisconnectedException discExc) {
1696                                handleDisconnectedException ();
1697                                break;
1698                        }
1699                }
1700                printer.printCallTree ();
1701        }
1702
1703        // process a JDI event
1704        private void handleEvent (Event event) {
1705                if (Trace.DEBUG) System.err.print (event.getClass ().getSimpleName ().replace ("EventImpl", ""));
1706
1707                // step event -- a line of code is about to be executed
1708                if (event instanceof StepEvent) {
1709                        stepEvent ((StepEvent) event);
1710                        return;
1711                }
1712
1713                // modified field event  -- a field is about to be changed
1714                if (event instanceof ModificationWatchpointEvent) {
1715                        modificationWatchpointEvent ((ModificationWatchpointEvent) event);
1716                        return;
1717                }
1718
1719                // method events
1720                if (event instanceof MethodEntryEvent) {
1721                        methodEntryEvent ((MethodEntryEvent) event);
1722                        return;
1723                }
1724                if (event instanceof MethodExitEvent) {
1725                        methodExitEvent ((MethodExitEvent) event);
1726                        return;
1727                }
1728                if (event instanceof ExceptionEvent) {
1729                        exceptionEvent ((ExceptionEvent) event);
1730                        return;
1731                }
1732
1733                // monitor events
1734                if (event instanceof MonitorContendedEnterEvent) {
1735                        monitorContendedEnterEvent ((MonitorContendedEnterEvent) event);
1736                        return;
1737                }
1738                if (event instanceof MonitorContendedEnteredEvent) {
1739                        monitorContendedEnteredEvent ((MonitorContendedEnteredEvent) event);
1740                        return;
1741                }
1742                if (event instanceof MonitorWaitEvent) {
1743                        monitorWaitEvent ((MonitorWaitEvent) event);
1744                        return;
1745                }
1746                if (event instanceof MonitorWaitedEvent) {
1747                        monitorWaitedEvent ((MonitorWaitedEvent) event);
1748                        return;
1749                }
1750
1751                // class events
1752                if (event instanceof ClassPrepareEvent) {
1753                        classPrepareEvent ((ClassPrepareEvent) event);
1754                        return;
1755                }
1756                if (event instanceof ClassUnloadEvent) {
1757                        classUnloadEvent ((ClassUnloadEvent) event);
1758                        return;
1759                }
1760
1761                // thread events
1762                if (event instanceof ThreadStartEvent) {
1763                        threadStartEvent ((ThreadStartEvent) event);
1764                        return;
1765                }
1766                if (event instanceof ThreadDeathEvent) {
1767                        threadDeathEvent ((ThreadDeathEvent) event);
1768                        return;
1769                }
1770
1771                // VM events
1772                if (event instanceof VMStartEvent) {
1773                        vmStartEvent ((VMStartEvent) event);
1774                        return;
1775                }
1776                if (event instanceof VMDeathEvent) {
1777                        vmDeathEvent ((VMDeathEvent) event);
1778                        return;
1779                }
1780                if (event instanceof VMDisconnectEvent) {
1781                        vmDisconnectEvent ((VMDisconnectEvent) event);
1782                        return;
1783                }
1784
1785                throw new Error ("\n!!!! Unexpected event type: " + event.getClass ().getCanonicalName ());
1786        }
1787
1788        // A VMDisconnectedException has occurred while dealing with another event.
1789        // Flush the event queue, dealing only with exit events (VMDeath,
1790        // VMDisconnect) so that things terminate correctly.
1791        private synchronized void handleDisconnectedException () {
1792                EventQueue queue = vm.eventQueue ();
1793                while (connected) {
1794                        try {
1795                                EventSet eventSet = queue.remove ();
1796                                for (Event event : eventSet) {
1797                                        if (event instanceof VMDeathEvent) vmDeathEvent ((VMDeathEvent) event);
1798                                        else if (event instanceof VMDisconnectEvent) vmDisconnectEvent ((VMDisconnectEvent) event);
1799                                }
1800                                eventSet.resume (); // resume the VM
1801                        } catch (InterruptedException e) {
1802                                // ignore
1803                        } catch (VMDisconnectedException e) {
1804                                // ignore
1805                        }
1806                }
1807        }
1808
1809        // ---------------------- VM event handling ----------------------------------
1810
1811        // Notification of initialization of a target VM. This event is received
1812        // before the main thread is started and before any application code has
1813        // been executed.
1814        private void vmStartEvent (VMStartEvent event) {
1815                vmDied = false;
1816                printer.vmStartEvent (event);
1817        }
1818
1819        // Notification of VM termination
1820        private void vmDeathEvent (VMDeathEvent event) {
1821                vmDied = true;
1822                printer.vmDeathEvent (event);
1823        }
1824
1825        // Notification of disconnection from the VM, either through normal
1826        // termination or because of an exception/error.
1827        private void vmDisconnectEvent (VMDisconnectEvent event) {
1828                connected = false;
1829                if (!vmDied) printer.vmDisconnectEvent (event);
1830        }
1831
1832        // -------------------- class event handling  ---------------
1833
1834        // a new class has been loaded
1835        private void classPrepareEvent (ClassPrepareEvent event) {
1836                ReferenceType type = event.referenceType ();
1837                String typeName = type.name ();
1838                if (Trace.CALLBACK_CLASS_NAME.equals (typeName) || Trace.GRAPHVIZ_CLASS_NAME.equals (typeName)) return;
1839                List<Field> fields = type.fields ();
1840
1841                // register field modification events
1842                EventRequestManager mgr = vm.eventRequestManager ();
1843                for (Field field : fields) {
1844                        ModificationWatchpointRequest req = mgr.createModificationWatchpointRequest (field);
1845                        for (String s : Trace.EXCLUDE_GLOBS)
1846                                req.addClassExclusionFilter (s);
1847                        req.setSuspendPolicy (EventRequest.SUSPEND_NONE);
1848                        req.enable ();
1849                }
1850                printer.classPrepareEvent (event);
1851
1852        }
1853        // a class has been unloaded
1854        private void classUnloadEvent (ClassUnloadEvent event) {
1855                if (!vmDied) printer.classUnloadEvent (event);
1856        }
1857
1858        // -------------------- thread event handling  ---------------
1859
1860        // a new thread has started running -- switch on single stepping
1861        private void threadStartEvent (ThreadStartEvent event) {
1862                ThreadReference thr = event.thread ();
1863                if (Format.ignoreThread (thr)) return;
1864                EventRequestManager mgr = vm.eventRequestManager ();
1865
1866                StepRequest sr = mgr.createStepRequest (thr, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
1867                sr.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
1868
1869                for (String s : Trace.EXCLUDE_GLOBS)
1870                        sr.addClassExclusionFilter (s);
1871                sr.enable ();
1872                printer.threadStartEvent (event);
1873        }
1874
1875        // the thread is about to terminate
1876        private void threadDeathEvent (ThreadDeathEvent event) {
1877                ThreadReference thr = event.thread ();
1878                if (Format.ignoreThread (thr)) return;
1879                printer.threadDeathEvent (event);
1880        }
1881
1882        // -------------------- delegated --------------------------------
1883
1884        private void methodEntryEvent (MethodEntryEvent event) {
1885                printer.methodEntryEvent (event);
1886        }
1887        private void methodExitEvent (MethodExitEvent event) {
1888                printer.methodExitEvent (event);
1889        }
1890        private void exceptionEvent (ExceptionEvent event) {
1891                printer.exceptionEvent (event);
1892        }
1893        private void stepEvent (StepEvent event) {
1894                printer.stepEvent (event);
1895        }
1896        private void modificationWatchpointEvent (ModificationWatchpointEvent event) {
1897                printer.modificationWatchpointEvent (event);
1898        }
1899        private void monitorContendedEnterEvent (MonitorContendedEnterEvent event) {
1900                printer.monitorContendedEnterEvent (event);
1901        }
1902        private void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) {
1903                printer.monitorContendedEnteredEvent (event);
1904        }
1905        private void monitorWaitEvent (MonitorWaitEvent event) {
1906                printer.monitorWaitEvent (event);
1907        }
1908        private void monitorWaitedEvent (MonitorWaitedEvent event) {
1909                printer.monitorWaitedEvent (event);
1910        }
1911}
1912
1913/**
1914 * Printer for events. Prints and updates the ValueMap. Handles Graphviz drawing
1915 * requests.
1916 *
1917 * @author James Riely, jriely@cs.depaul.edu, August 2014
1918 */
1919/* private static */interface IndentPrinter {
1920        public void println (ThreadReference thr, String string);
1921}
1922
1923/* private static */interface JDIEventHandler {
1924        public void printCallTree ();
1925        /** Notification of target VM termination. */
1926        public void vmDeathEvent (VMDeathEvent event);
1927        /** Notification of disconnection from target VM. */
1928        public void vmDisconnectEvent (VMDisconnectEvent event);
1929        /** Notification of initialization of a target VM. */
1930        public void vmStartEvent (VMStartEvent event);
1931        /** Notification of a new running thread in the target VM. */
1932        public void threadStartEvent (ThreadStartEvent event);
1933        /** Notification of a completed thread in the target VM. */
1934        public void threadDeathEvent (ThreadDeathEvent event);
1935        /** Notification of a class prepare in the target VM. */
1936        public void classPrepareEvent (ClassPrepareEvent event);
1937        /** Notification of a class unload in the target VM. */
1938        public void classUnloadEvent (ClassUnloadEvent event);
1939        /** Notification of a field access in the target VM. */
1940        //public void accessWatchpointEvent (AccessWatchpointEvent event);
1941        /** Notification of a field modification in the target VM. */
1942        public void modificationWatchpointEvent (ModificationWatchpointEvent event);
1943        /** Notification of a method invocation in the target VM. */
1944        public void methodEntryEvent (MethodEntryEvent event);
1945        /** Notification of a method return in the target VM. */
1946        public void methodExitEvent (MethodExitEvent event);
1947        /** Notification of an exception in the target VM. */
1948        public void exceptionEvent (ExceptionEvent event);
1949        /** Notification of step completion in the target VM. */
1950        public void stepEvent (StepEvent event);
1951        /** Notification of a breakpoint in the target VM. */
1952        //public void breakpointEvent (BreakpointEvent event);
1953        /**
1954         * Notification that a thread in the target VM is attempting to enter a
1955         * monitor that is already acquired by another thread.
1956         */
1957        public void monitorContendedEnterEvent (MonitorContendedEnterEvent event);
1958        /**
1959         * Notification that a thread in the target VM is entering a monitor after
1960         * waiting for it to be released by another thread.
1961         */
1962        public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event);
1963        /**
1964         * Notification that a thread in the target VM is about to wait on a monitor
1965         * object.
1966         */
1967        public void monitorWaitEvent (MonitorWaitEvent event);
1968        /**
1969         * Notification that a thread in the target VM has finished waiting on an
1970         * monitor object.
1971         */
1972        public void monitorWaitedEvent (MonitorWaitedEvent event);
1973}
1974
1975/* private static */class Printer implements IndentPrinter, JDIEventHandler {
1976        private final Set<ReferenceType> staticClasses = new HashSet<> ();
1977        private final Map<ThreadReference, Value> returnValues = new HashMap<> ();
1978        private final Map<ThreadReference, Value> exceptionsMap = new HashMap<> ();
1979        private final ValueMap values = new ValueMap ();
1980        private final CodeMap codeMap = new CodeMap ();
1981        private final InsideIgnoredMethodMap boolMap = new InsideIgnoredMethodMap ();
1982
1983        public void monitorContendedEnterEvent (MonitorContendedEnterEvent event) {}
1984        public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) {}
1985        public void monitorWaitEvent (MonitorWaitEvent event) {}
1986        public void monitorWaitedEvent (MonitorWaitedEvent event) {}
1987
1988        public void vmStartEvent (VMStartEvent event) {
1989                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Started");
1990        }
1991        public void vmDeathEvent (VMDeathEvent event) {
1992                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Stopped");
1993        }
1994        public void vmDisconnectEvent (VMDisconnectEvent event) {
1995                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Disconnected application");
1996        }
1997        public void threadStartEvent (ThreadStartEvent event) {
1998                ThreadReference thr = event.thread ();
1999                values.stackCreate (thr);
2000                boolMap.addThread (thr);
2001                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread started: " + thr.name ());
2002        }
2003        public void threadDeathEvent (ThreadDeathEvent event) {
2004                ThreadReference thr = event.thread ();
2005                values.stackDestroy (thr);
2006                boolMap.removeThread (thr);
2007                if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread stopped: " + thr.name ());
2008        }
2009        public void classPrepareEvent (ClassPrepareEvent event) {
2010
2011                ReferenceType ref = event.referenceType ();
2012
2013                List<Field> fields = ref.fields ();
2014                List<Method> methods = ref.methods ();
2015
2016                String filename;
2017                try {
2018                        filename = ref.sourcePaths (null).get (0); // get filename of the class
2019                        codeMap.addFile (filename);
2020                } catch (AbsentInformationException e) {
2021                        filename = "??";
2022                }
2023
2024                boolean hasConstructors = false;
2025                boolean hasObjectMethods = false;
2026                boolean hasClassMethods = false;
2027                boolean hasClassFields = false;
2028                boolean hasObjectFields = false;
2029                for (Method m : methods) {
2030                        if (Format.isConstructor (m)) hasConstructors = true;
2031                        if (Format.isObjectMethod (m)) hasObjectMethods = true;
2032                        if (Format.isClassMethod (m)) hasClassMethods = true;
2033                }
2034                for (Field f : fields) {
2035                        if (Format.isStaticField (f)) hasClassFields = true;
2036                        if (Format.isObjectField (f)) hasObjectFields = true;
2037                }
2038
2039                if (hasClassFields) {
2040                        staticClasses.add (ref);
2041                }
2042                if (Trace.CONSOLE_SHOW_CLASSES) {
2043                        println ("|||| loaded class: " + ref.name () + " from " + filename);
2044                        if (hasClassFields) {
2045                                println ("||||  class fields: ");
2046                                for (Field f : fields)
2047                                        if (Format.isStaticField (f)) println ("||||    " + Format.fieldToString (f));
2048                        }
2049                        if (hasClassMethods) {
2050                                println ("||||  class methods: ");
2051                                for (Method m : methods)
2052                                        if (Format.isClassMethod (m)) println ("||||    " + Format.methodToString (m, false));
2053                        }
2054                        if (hasConstructors) {
2055                                println ("||||  constructors: ");
2056                                for (Method m : methods)
2057                                        if (Format.isConstructor (m)) println ("||||    " + Format.methodToString (m, false));
2058                        }
2059                        if (hasObjectFields) {
2060                                println ("||||  object fields: ");
2061                                for (Field f : fields)
2062                                        if (Format.isObjectField (f)) println ("||||    " + Format.fieldToString (f));
2063                        }
2064                        if (hasObjectMethods) {
2065                                println ("||||  object methods: ");
2066                                for (Method m : methods)
2067                                        if (Format.isObjectMethod (m)) println ("||||    " + Format.methodToString (m, false));
2068                        }
2069                }
2070        }
2071        public void classUnloadEvent (ClassUnloadEvent event) {
2072                if (Trace.CONSOLE_SHOW_CLASSES) println ("|||| unloaded class: " + event.className ());
2073        }
2074
2075        public void methodEntryEvent (MethodEntryEvent event) {
2076                Method meth = event.method ();
2077                ThreadReference thr = event.thread ();
2078                String calledMethodClassname = meth.declaringType ().name ();
2079                //System.err.println (calledMethodClassname);
2080                //System.err.println (Trace.GRAPHVIZ_CLASS_NAME);
2081                if (Format.matchesExcludePrefix (calledMethodClassname)) return;
2082                if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return;
2083                if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return;
2084
2085                if (!Trace.CALLBACK_CLASS_NAME.equals (calledMethodClassname)) {
2086                        StackFrame currFrame = Format.getFrame (meth, thr);
2087                        values.stackPushFrame (currFrame, thr);
2088                        if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) {
2089                                println (thr, ">>>> " + Format.methodToString (meth, true)); // + "[" + thr.name () + "]");
2090                                printLocals (currFrame, thr);
2091                        }
2092                } else {
2093                        // COPY PASTE HORRORS HERE
2094                        boolMap.enteringIgnoredMethod (thr);
2095                        String name = meth.name ();
2096                        if (Trace.CALLBACK_CLEAR_CALL_TREE.equals (name)) {
2097                                values.clearCallTree();
2098                        } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHOD.equals (name)) {
2099                                List<StackFrame> frames;
2100                                try {
2101                                        frames = thr.frames ();
2102                                } catch (IncompatibleThreadStateException e) {
2103                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2104                                }
2105                                StackFrame currFrame = Format.getFrame (meth, thr);
2106                                List<LocalVariable> locals;
2107                                try {
2108                                        locals = currFrame.visibleVariables ();
2109                                } catch (AbsentInformationException e) {
2110                                        return;
2111                                }
2112                                StringReference obj = (StringReference) currFrame.getValue (locals.get (0));
2113                                Trace.drawStepsOfMethodBegin (obj.value());
2114                                returnValues.put (thr, null);
2115                        } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHODS.equals (name)) {
2116                                List<StackFrame> frames;
2117                                try {
2118                                        frames = thr.frames ();
2119                                } catch (IncompatibleThreadStateException e) {
2120                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2121                                }
2122                                StackFrame currFrame = Format.getFrame (meth, thr);
2123                                List<LocalVariable> locals;
2124                                try {
2125                                        locals = currFrame.visibleVariables ();
2126                                } catch (AbsentInformationException e) {
2127                                        return;
2128                                }
2129                                ArrayReference arr = (ArrayReference) currFrame.getValue (locals.get (0)); 
2130                                for (int i = arr.length() - 1; i >= 0; i--) {
2131                                        StringReference obj = (StringReference) arr.getValue (i);
2132                                        Trace.drawStepsOfMethodBegin (obj.value());
2133                                }
2134                                returnValues.put (thr, null);
2135                        } else if (Trace.CALLBACK_DRAW_STEPS_BEGIN.equals (name)) {
2136                                Trace.GRAPHVIZ_SHOW_STEPS = true;
2137                                returnValues.put (thr, null);
2138                        } else if (Trace.CALLBACK_DRAW_STEPS_END.equals (name)) {
2139                                Trace.drawStepsOfMethodEnd ();
2140                        } else if (Trace.CALLBACKS.contains (name)) {
2141                                //System.err.println (calledMethodClassname + ":" + Trace.SPECIAL_METHOD_NAME + ":" + meth.name ());
2142                                StackFrame frame;
2143                                try {
2144                                        frame = thr.frame (1);
2145                                } catch (IncompatibleThreadStateException e) {
2146                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2147                                }
2148                                Location loc = frame.location ();
2149                                String label = Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ());
2150                                drawGraph (label, thr, meth);
2151                                if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ());
2152                        }
2153                }
2154        }
2155        public void methodExitEvent (MethodExitEvent event) {
2156                ThreadReference thr = event.thread ();
2157                Method meth = event.method ();
2158                String calledMethodClassname = meth.declaringType ().name ();
2159                if (Format.matchesExcludePrefix (calledMethodClassname)) return;
2160                if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return;
2161                if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return;
2162                if (boolMap.leavingIgnoredMethod (thr)) return;
2163                if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) {
2164                        Type returnType;
2165                        try {
2166                                returnType = meth.returnType ();
2167                        } catch (ClassNotLoadedException e) {
2168                                returnType = null;
2169                        }
2170                        if (returnType instanceof VoidType) {
2171                                println (thr, "<<<< " + Format.methodToString (meth, true));
2172                        } else {
2173                                println (thr, "<<<< " + Format.methodToString (meth, true) + " : " + Format.valueToString (event.returnValue ()));
2174                        }
2175                }
2176                values.stackPopFrame (thr);
2177                StackFrame currFrame = Format.getFrame (meth, thr);
2178                if (meth.isConstructor ()) {
2179                        returnValues.put (thr, currFrame.thisObject ());
2180                } else {
2181                        returnValues.put (thr, event.returnValue ());
2182                }
2183        }
2184        public void exceptionEvent (ExceptionEvent event) {
2185                ThreadReference thr = event.thread ();
2186                try {
2187                        StackFrame currentFrame = thr.frame (0);
2188                } catch (IncompatibleThreadStateException e) {
2189                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2190                }
2191                ObjectReference exception = event.exception ();
2192                Location catchLocation = event.catchLocation ();
2193                //String name = Format.objectToStringLong (exception);
2194                String message = "()";
2195                Field messageField = exception.referenceType ().fieldByName ("detailMessage");
2196                if (messageField != null) {
2197                        Value value = exception.getValue (messageField);
2198                        if (value != null) {
2199                                message = "(" + value.toString () + ")";
2200                        }
2201                }
2202                String name = Format.shortenFullyQualifiedName (exception.referenceType ().name ()) + message;
2203
2204                if (catchLocation == null) {
2205                        // uncaught exception
2206                        if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! UNCAUGHT EXCEPTION: " + name);
2207                        if (Trace.GRAPHVIZ_SHOW_STEPS) Graphviz.drawFramesCheck (null, null, event.exception (), null, staticClasses);
2208                } else {
2209                        if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! EXCEPTION: " + name);
2210                        if (Trace.GRAPHVIZ_SHOW_STEPS) exceptionsMap.put (thr, event.exception ());
2211                }
2212        }
2213        public void stepEvent (StepEvent event) {
2214                ThreadReference thr = event.thread ();
2215                if (boolMap.insideIgnoredMethod (thr)) {
2216                        //System.err.println ("ignored");
2217                        return;
2218                }
2219                values.maybeAdjustAfterException (thr);
2220
2221                Location loc = event.location ();
2222                String filename;
2223                try {
2224                        filename = loc.sourcePath ();
2225                } catch (AbsentInformationException e) {
2226                        return;
2227                }
2228                if (Trace.CONSOLE_SHOW_STEPS) {
2229                        values.stackUpdateFrame (event.location ().method (), thr, this);
2230                        int lineNumber = loc.lineNumber ();
2231                        if (Trace.CONSOLE_SHOW_STEPS_VERBOSE) {
2232                                println (thr, Format.shortenFilename (filename) + ":" + lineNumber + codeMap.show (filename, lineNumber));
2233                        } else {
2234                                printLineNum (thr, lineNumber);
2235                        }
2236                }
2237                if (Trace.GRAPHVIZ_SHOW_STEPS) {
2238                        try {
2239                                Graphviz.drawFramesCheck (Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ()), returnValues.get (thr),
2240                                                exceptionsMap.get (thr), thr.frames (), staticClasses);
2241                                if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ());
2242                                returnValues.put (thr, null);
2243                                exceptionsMap.put (thr, null);
2244                        } catch (IncompatibleThreadStateException e) {
2245                                throw new Error (Trace.BAD_ERROR_MESSAGE);
2246                        }
2247                }
2248
2249        }
2250        public void modificationWatchpointEvent (ModificationWatchpointEvent event) {
2251                ThreadReference thr = event.thread ();
2252                if (boolMap.insideIgnoredMethod (thr)) return;
2253                if (!Trace.CONSOLE_SHOW_STEPS) return;
2254                Field f = event.field ();
2255                Value value = event.valueToBe (); // value that _will_ be assigned
2256                String debug = Trace.DEBUG ? "#5" + "[" + thr.name () + "]" : "";
2257                Type type;
2258                try {
2259                        type = f.type ();
2260                } catch (ClassNotLoadedException e) {
2261                        type = null; // waiting for class to load
2262                }
2263
2264                if (value instanceof ArrayReference) {
2265                        if (f.isStatic ()) {
2266                                String name = Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name ();
2267                                if (values.registerStaticArray ((ArrayReference) value, name)) {
2268                                        println (thr, "  " + debug + "> " + name + " = " + Format.valueToString (value));
2269                                }
2270                        }
2271                        return; // array types are handled separately -- this avoids redundant printing
2272                }
2273                ObjectReference objRef = event.object ();
2274                if (objRef == null) {
2275                        println (thr, "  " + debug + "> " + Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name () + " = " + Format.valueToString (value));
2276                } else {
2277                        // changes to array references are printed by updateFrame
2278                        if (Format.tooManyFields (objRef)) {
2279                                println (thr, "  " + debug + "> " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (value));
2280                        } else {
2281                                println (thr, "  " + debug + "> this = " + Format.objectToStringLong (objRef));
2282                        }
2283                }
2284
2285        }
2286
2287        public void printCallTree () { values.printCallTree(); }
2288        private void drawGraph (String loc, ThreadReference thr, Method meth) {
2289                List<StackFrame> frames;
2290                try {
2291                        frames = thr.frames ();
2292                } catch (IncompatibleThreadStateException e) {
2293                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2294                }
2295                //setDrawPrefixFromParameter (Format.getFrame (meth, thr), meth);
2296                StackFrame currFrame = Format.getFrame (meth, thr);
2297                List<LocalVariable> locals;
2298                try {
2299                        locals = currFrame.visibleVariables ();
2300                } catch (AbsentInformationException e) {
2301                        return;
2302                }
2303                String name = meth.name ();
2304                if (Trace.CALLBACK_DRAW_THIS_FRAME.equals (name)) {
2305                        Graphviz.drawFrames (1, 2, loc, null, null, frames, staticClasses, false);
2306                } else if (Trace.CALLBACK_DRAW_ALL_FRAMES_AND_STATICS.equals (name)) {
2307                        Graphviz.drawFrames (1, frames==null?0:frames.size(), loc, null, null, frames, staticClasses, true);
2308                } else if (Trace.CALLBACK_DRAW_ALL_FRAMES.equals (name) || locals.size () == 0) {
2309                        Graphviz.drawFrames (1, frames==null?0:frames.size(), loc, null, null, frames, staticClasses, false);
2310                } else if (Trace.CALLBACK_DRAW_OBJECT.equals (name)) {
2311                        ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (0));
2312                        Map<String, ObjectReference> objects = new HashMap<> ();
2313                        objects.put (Graphviz.PREFIX_UNUSED_LABEL, obj);
2314                        Graphviz.drawObjects (loc, objects);
2315                } else if (Trace.CALLBACK_DRAW_OBJECT_NAMED.equals (name)) {
2316                        StringReference str = (StringReference) currFrame.getValue (locals.get (0));
2317                        ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (1));
2318                        Map<String, ObjectReference> objects = new HashMap<> ();
2319                        objects.put (str.value (), obj);
2320                        Graphviz.drawObjects (loc, objects);
2321                } else if (Trace.CALLBACK_DRAW_OBJECTS_NAMED.equals (name)) {
2322                        ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0));
2323                        Map<String, ObjectReference> objects = new HashMap<> ();
2324                        int n = args.length ();
2325                        if (n % 2 != 0) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED + " requires an even number of parameters, alternating strings and objects.");
2326                        for (int i = 0; i < n; i += 2) {
2327                                Value str = args.getValue (i);
2328                                if (!(str instanceof StringReference)) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED
2329                                                + " requires an even number of parameters, alternating strings and objects.");
2330                                objects.put (((StringReference) str).value (), (ObjectReference) args.getValue (i + 1));
2331                        }
2332                        Graphviz.drawObjects (loc, objects);
2333                } else {
2334                        ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0));
2335                        Map<String, ObjectReference> objects = new HashMap<> ();
2336                        int n = args.length ();
2337                        for (int i = 0; i < n; i++) {
2338                                objects.put (Graphviz.PREFIX_UNUSED_LABEL + i, (ObjectReference) args.getValue (i));
2339                        }
2340                        Graphviz.drawObjects (loc, objects);
2341                }
2342        }
2343        // This method was used to set the filename from the parameter of the draw method.
2344        // Not using this any more.
2345        //      private void setDrawPrefixFromParameter (StackFrame currFrame, Method meth) {
2346        //              String prefix = null;
2347        //              List<LocalVariable> locals;
2348        //              try {
2349        //                      locals = currFrame.visibleVariables ();
2350        //              } catch (AbsentInformationException e) {
2351        //                      return;
2352        //              }
2353        //              if (locals.size () >= 1) {
2354        //                      Value v = currFrame.getValue (locals.get (0));
2355        //                      if (!(v instanceof StringReference)) throw new Error ("\n!!!! " + meth.name () + " must have at most a single parameter."
2356        //                                      + "\n!!!! The parameter must be of type String");
2357        //                      prefix = ((StringReference) v).value ();
2358        //                      if (prefix != null) {
2359        //                              Graphviz.setOutputFilenamePrefix (prefix);
2360        //                      }
2361        //              }
2362        //      }
2363
2364        // ---------------------- print locals ----------------------------------
2365
2366        private void printLocals (StackFrame currFrame, ThreadReference thr) {
2367                List<LocalVariable> locals;
2368                try {
2369                        locals = currFrame.visibleVariables ();
2370                } catch (AbsentInformationException e) {
2371                        return;
2372                }
2373                String debug = Trace.DEBUG ? "#3" : "";
2374
2375                ObjectReference objRef = currFrame.thisObject (); // get 'this' object
2376                if (objRef != null) {
2377                        if (Format.tooManyFields (objRef)) {
2378                                println (thr, "  " + debug + "this: " + Format.objectToStringShort (objRef));
2379                                ReferenceType type = objRef.referenceType (); // get type (class) of object
2380                                List<Field> fields; // use allFields() to include inherited fields
2381                                try {
2382                                        fields = type.fields ();
2383                                } catch (ClassNotPreparedException e) {
2384                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2385                                }
2386
2387                                //println (thr, "  fields: ");
2388                                for (Field f : fields) {
2389                                        if (!Format.isObjectField (f)) continue;
2390                                        println (thr, "  " + debug + "| " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f)));
2391                                }
2392                                if (locals.size () > 0) println (thr, "  locals: ");
2393                        } else {
2394                                println (thr, "  " + debug + "| this = " + Format.objectToStringLong (objRef));
2395                        }
2396                }
2397                for (LocalVariable l : locals)
2398                        println (thr, "  " + debug + "| " + l.name () + " = " + Format.valueToString (currFrame.getValue (l)));
2399        }
2400
2401        // ---------------------- indented printing ----------------------------------
2402
2403        private boolean atNewLine = true;
2404        private static PrintStream out = System.out;
2405        public static void setFilename (String s) {
2406                try {
2407                        Printer.out = new PrintStream (s);
2408                } catch (FileNotFoundException e) {
2409                        System.err.println ("Attempting setFilename \"" + s + "\"");
2410                        System.err.println ("Cannot open file \"" + s + "\" for writing; using the console for output.");
2411                }
2412        }
2413        public static void setFilename () {
2414                Printer.out = System.out;
2415        }
2416        public void println (String string) {
2417                if (!atNewLine) {
2418                        atNewLine = true;
2419                        Printer.out.println ();
2420                }
2421                Printer.out.println (string);
2422        }
2423        public void println (ThreadReference thr, String string) {
2424                if (!atNewLine) {
2425                        atNewLine = true;
2426                        Printer.out.println ();
2427                }
2428                if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ());
2429                int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0;
2430                for (int i = 1; i < numFrames; i++)
2431                        Printer.out.print ("  ");
2432                Printer.out.println (string);
2433        }
2434        private void printLinePrefix (ThreadReference thr, boolean showLinePrompt) {
2435                if (atNewLine) {
2436                        atNewLine = false;
2437                        if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ());
2438                        int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0;
2439                        for (int i = 1; i < numFrames; i++)
2440                                Printer.out.print ("  ");
2441                        if (showLinePrompt) Printer.out.print ("  Line: ");
2442                }
2443        }
2444        public void printLineNum (ThreadReference thr, int lineNumber) {
2445                printLinePrefix (thr, true);
2446                Printer.out.print (lineNumber + " ");
2447        }
2448        public void printDrawEvent (ThreadReference thr, String filename) {
2449                printLinePrefix (thr, false);
2450                Printer.out.print ("#" + filename + "# ");
2451        }
2452}
2453
2454/**
2455 * Code for formatting values and other static utilities.
2456 *
2457 * @author James Riely, jriely@cs.depaul.edu, August 2014
2458 */
2459/* private static */class Format {
2460        private Format () {}; // noninstantiable class
2461
2462        public static StackFrame getFrame (Method meth, ThreadReference thr) {
2463                Type methDeclaredType = meth.declaringType ();
2464                int frameNumber = -1;
2465                StackFrame currFrame;
2466                try {
2467                        do {
2468                                frameNumber++;
2469                                currFrame = thr.frame (frameNumber);
2470                        } while (methDeclaredType != currFrame.location ().declaringType ());
2471                } catch (IncompatibleThreadStateException e) {
2472                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2473                }
2474                return currFrame;
2475        }
2476        // http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
2477        public static String glob2regex (String glob) {
2478                StringBuilder regex = new StringBuilder ("^");
2479                for(int i = 0; i < glob.length(); ++i) {
2480                        final char c = glob.charAt(i);
2481                        switch(c) {
2482                        case '*': regex.append (".*"); break;
2483                        case '.': regex.append ("\\."); break;
2484                        case '$': regex.append ("\\$"); break;
2485                        default: regex.append (c);
2486                        }
2487                }
2488                regex.append ('$');
2489                return regex.toString ();
2490        }
2491        private static final ArrayList<String> EXCLUDE_REGEX;
2492        private static final ArrayList<String> DRAWING_INCLUDE_REGEX;
2493        static {
2494                EXCLUDE_REGEX = new ArrayList<> ();
2495                for (String s : Trace.EXCLUDE_GLOBS) {
2496                        //System.err.println (glob2regex (s));
2497                        EXCLUDE_REGEX.add (glob2regex (s));
2498                }
2499                DRAWING_INCLUDE_REGEX = new ArrayList<> ();
2500                for (String s : Trace.DRAWING_INCLUDE_GLOBS) {
2501                        DRAWING_INCLUDE_REGEX.add (glob2regex (s));
2502                }
2503        }
2504        public static boolean matchesExcludePrefix (String typeName) {
2505                //System.err.println (typeName + ":" + Trace.class.getName());
2506                if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && "java.lang.String".equals (typeName)) return false;
2507                // don't explore objects on the exclude list
2508                for (String regex : Format.EXCLUDE_REGEX)
2509                        if (typeName.matches (regex)) return true;
2510                return false;
2511        }
2512        public static boolean matchesExcludePrefixShow (String typeName) {
2513                for (String regex : Format.DRAWING_INCLUDE_REGEX)
2514                        if (typeName.matches (regex)) return false;
2515                return matchesExcludePrefix (typeName);
2516        }
2517        public static String valueToString (Value value) {
2518                return valueToString (false, new HashSet<> (), value);
2519        }
2520        private static String valueToString (boolean inArray, Set<Value> visited, Value value) {
2521                if (value == null) return "null";
2522                if (value instanceof PrimitiveValue) return value.toString ();
2523                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString ();
2524                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return wrapperToString ((ObjectReference) value);
2525                return objectToStringLong (inArray, visited, (ObjectReference) value);
2526        }
2527        public static String valueToStringShort (Value value) {
2528                if (value == null) return "null";
2529                if (value instanceof PrimitiveValue) return value.toString ();
2530                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString ();
2531                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return value.toString (); //wrapperToString ((ObjectReference) value);
2532                return objectToStringShort ((ObjectReference) value);
2533        }
2534        public static boolean isWrapper (Type type) {
2535                if (!(type instanceof ReferenceType)) return false;
2536                if (type instanceof ArrayType) return false;
2537                String fqn = type.name ();
2538                if (!fqn.startsWith ("java.lang.")) return false;
2539                String className = fqn.substring (10);
2540                if (className.equals ("String")) return false;
2541                return (className.equals ("Integer") || className.equals ("Double") || className.equals ("Float") || className.equals ("Long") || className.equals ("Character")
2542                                || className.equals ("Short") || className.equals ("Byte") || className.equals ("Boolean"));
2543        }
2544        public static String wrapperToString (ObjectReference obj) {
2545                Object xObject;
2546                if (obj == null) return "null";
2547                ReferenceType cz = (ReferenceType) obj.type ();
2548                String fqn = cz.name ();
2549                String className = fqn.substring (10);
2550                Field field = cz.fieldByName ("value");
2551                return obj.getValue (field).toString ();
2552        }
2553        public static String objectToStringShort (ObjectReference objRef) {
2554                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (objRef.type ().name ()) + "@" + objRef.uniqueID ();
2555                else return "@" + objRef.uniqueID ();
2556        }
2557        private static String emptyArrayToStringShort (ArrayReference arrayRef, int length) {
2558                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) {
2559                        String classname = shortenFullyQualifiedName (arrayRef.type ().name ());
2560                        return classname.substring (0, classname.indexOf ("[")) + "[" + length + "]@" + arrayRef.uniqueID ();
2561                } else {
2562                        return "@" + arrayRef.uniqueID ();
2563                }
2564        }
2565        private static String nonemptyArrayToStringShort (ArrayReference arrayRef, int length) {
2566                if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (arrayRef.getValue (0).type ().name ()) + "[" + length + "]@" + arrayRef.uniqueID ();
2567                else return "@" + arrayRef.uniqueID ();
2568        }
2569
2570        public static String objectToStringLong (ObjectReference objRef) {
2571                return objectToStringLong (false, new HashSet<> (), objRef);
2572        }
2573        private static String objectToStringLong (boolean inArray, Set<Value> visited, ObjectReference objRef) {
2574                if (!visited.add (objRef)) return objectToStringShort (objRef);
2575                StringBuilder result = new StringBuilder ();
2576                if (objRef == null) {
2577                        return "null";
2578                } else if (objRef instanceof ArrayReference) {
2579                        ArrayReference arrayRef = (ArrayReference) objRef;
2580                        int length = arrayRef.length ();
2581                        if (length == 0 || arrayRef.getValue (0) == null) {
2582                                if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) {
2583                                        result.append (emptyArrayToStringShort (arrayRef, length));
2584                                        result.append (" ");
2585                                }
2586                                result.append ("[ ] ");
2587                        } else {
2588                                if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) {
2589                                        result.append (nonemptyArrayToStringShort (arrayRef, length));
2590                                        result.append (" ");
2591                                }
2592                                result.append ("[ ");
2593                                int max = (arrayRef.getValue (0) instanceof PrimitiveValue) ? Trace.CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE : Trace.CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT;
2594                                int i = 0;
2595                                while (i < length && i < max) {
2596                                        result.append (valueToString (true, visited, arrayRef.getValue (i)));
2597                                        i++;
2598                                        if (i < length) result.append (", ");
2599                                }
2600                                if (i < length) result.append ("...");
2601                                result.append (" ]");
2602                        }
2603                } else {
2604                        result.append (objectToStringShort (objRef));
2605                        ReferenceType type = objRef.referenceType (); // get type (class) of object
2606
2607                        // don't explore objects on the exclude list
2608                        if (!Format.matchesExcludePrefixShow (type.name ())) {
2609                                Iterator<Field> fields; // use allFields() to include inherited fields
2610                                try {
2611                                        fields = type.fields ().iterator ();
2612                                } catch (ClassNotPreparedException e) {
2613                                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2614                                }
2615                                if (fields.hasNext ()) {
2616                                        result.append (" { ");
2617                                        int i = 0;
2618                                        while (fields.hasNext () && i < Trace.CONSOLE_MAX_FIELDS) {
2619                                                Field f = fields.next ();
2620                                                if (!isObjectField (f)) continue;
2621                                                if (i != 0) result.append (", ");
2622                                                result.append (f.name ());
2623                                                result.append ("=");
2624                                                result.append (valueToString (inArray, visited, objRef.getValue (f)));
2625                                                i++;
2626                                        }
2627                                        if (fields.hasNext ()) result.append ("...");
2628                                        result.append (" }");
2629                                }
2630                        }
2631                }
2632                return result.toString ();
2633        }
2634
2635        // ---------------------- static utilities ----------------------------------
2636
2637        public static boolean ignoreThread (ThreadReference thr) {
2638                if (thr.name ().equals ("Signal Dispatcher") || thr.name ().equals ("DestroyJavaVM") || thr.name ().startsWith ("AWT-")) return true; // ignore AWT threads
2639                if (thr.threadGroup ().name ().equals ("system")) return true; // ignore system threads
2640                return false;
2641        }
2642
2643        public static boolean isStaticField (Field f) {
2644                if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false;
2645                return f.isStatic ();
2646        }
2647        public static boolean isObjectField (Field f) {
2648                if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false;
2649                return !f.isStatic ();
2650        }
2651        public static boolean isConstructor (Method m) {
2652                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2653                return m.isConstructor ();
2654        }
2655        public static boolean isObjectMethod (Method m) {
2656                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2657                return !m.isConstructor () && !m.isStatic ();
2658        }
2659        public static boolean isClassMethod (Method m) {
2660                if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false;
2661                return m.isStatic ();
2662        }
2663        public static boolean tooManyFields (ObjectReference objRef) {
2664                int count = 0;
2665                ReferenceType type = ((ReferenceType) objRef.type ());
2666                for (Field field : type.fields ())
2667                        if (isObjectField (field)) count++;
2668                return count > Trace.CONSOLE_MAX_FIELDS;
2669        }
2670        public static String shortenFullyQualifiedName (String fqn) {
2671                if (Trace.SHOW_PACKAGE_IN_CLASS_NAME || !fqn.contains (".")) return fqn;
2672                String className = fqn.substring (1 + fqn.lastIndexOf ("."));
2673                if (Trace.SHOW_OUTER_CLASS_IN_CLASS_NAME || !className.contains ("$")) return className;
2674                return className.substring (1 + className.lastIndexOf ("$"));
2675        }
2676        public static String shortenFilename (String fn) {
2677                if (!fn.contains ("/")) return fn;
2678                return fn.substring (1 + fn.lastIndexOf ("/"));
2679        }
2680        public static String fieldToString (Field f) {
2681                StringBuilder result = new StringBuilder ();
2682                if (f.isPrivate ()) result.append ("- ");
2683                if (f.isPublic ()) result.append ("+ ");
2684                if (f.isPackagePrivate ()) result.append ("~ ");
2685                if (f.isProtected ()) result.append ("# ");
2686                result.append (shortenFullyQualifiedName (f.name ()));
2687                result.append (" : ");
2688                result.append (shortenFullyQualifiedName (f.typeName ()));
2689                return result.toString ();
2690        }
2691        public static String methodToString (Method m, boolean showClass) {
2692                return methodToString (m, showClass, true, ".");
2693        }
2694        public static String methodToString (Method m, boolean showClass, boolean showParameters, String dotCharacter) {
2695                String className = shortenFullyQualifiedName (m.declaringType ().name ());
2696                StringBuilder result = new StringBuilder ();
2697                if (!showClass && showParameters) {
2698                        if (m.isPrivate ()) result.append ("- ");
2699                        if (m.isPublic ()) result.append ("+ ");
2700                        if (m.isPackagePrivate ()) result.append ("~ ");
2701                        if (m.isProtected ()) result.append ("# ");
2702                }
2703                if (m.isConstructor ()) {
2704                        result.append (className);
2705                } else if (m.isStaticInitializer ()) {
2706                        result.append (className);
2707                        result.append (".CLASS_INITIALIZER");
2708                        return result.toString ();
2709                } else {
2710                        if (showClass) {
2711                                result.append (className);
2712                                result.append (dotCharacter);
2713                        }
2714                        result.append (shortenFullyQualifiedName (m.name ()));
2715                }
2716                if (showParameters) {
2717                        result.append ("(");
2718                        Iterator<LocalVariable> vars;
2719                        try {
2720                                vars = m.arguments ().iterator ();
2721                                while (vars.hasNext ()) {
2722                                        result.append (shortenFullyQualifiedName (vars.next ().typeName ()));
2723                                        if (vars.hasNext ()) result.append (", ");
2724                                }
2725                        } catch (AbsentInformationException e) {
2726                                result.append ("??");
2727                        }
2728                        result.append (")");
2729                }
2730                //result.append (" from ");
2731                //result.append (m.declaringType ());
2732                return result.toString ();
2733        }
2734}
2735
2736/**
2737 * A map from filenames to file contents. Allows lines to be printed.
2738 *
2739 * changes: Riely inlined the "ShowLines" class.
2740 *
2741 * @author Andrew Davison, March 2009, ad@fivedots.coe.psu.ac.th
2742 * @author James Riely
2743 **/
2744/* private static */class CodeMap {
2745        private TreeMap<String, ArrayList<String>> listings = new TreeMap<> ();
2746
2747        // add filename-ShowLines pair to map
2748        public void addFile (String filename) {
2749                if (listings.containsKey (filename)) {
2750                        //System.err.println (filename + "already listed");
2751                        return;
2752                }
2753
2754                String srcFilename = null;
2755                for (var s : Trace.POSSIBLE_SRC_LOCATIONS) {
2756                        String f = s + File.separator + filename;
2757                        if (Files.exists(Path.of(f))) {
2758                                srcFilename = f;
2759                        }
2760                }
2761                if (srcFilename == null) {
2762                        System.err.println ("\n!!!! Source not found in POSSIBLE_SRC_LOCATIONS: " + filename);
2763                        return;
2764                }
2765                ArrayList<String> code = new ArrayList<> ();
2766                BufferedReader in = null;
2767                try {
2768                        in = new BufferedReader (new FileReader (srcFilename));
2769                        String line;
2770                        while ((line = in.readLine ()) != null)
2771                                code.add (line);
2772                } catch (IOException ex) {
2773                        System.err.println ("\n!!!! Could not read " + srcFilename);
2774                } finally {
2775                        try {
2776                                if (in != null) in.close ();
2777                        } catch (IOException e) {
2778                                throw new Error ("\n!!!! Problem reading " + srcFilename);
2779                        }
2780                }
2781                listings.put (filename, code);
2782                //println (filename + " added to listings");
2783        }
2784
2785        // return the specified line from filename
2786        public String show (String filename, int lineNumber) {
2787                ArrayList<String> code = listings.get (filename);
2788                if (code == null) return (filename + " not listed");
2789                if ((lineNumber < 1) || (lineNumber > code.size ())) return " [Could not load source file.]";
2790                return (code.get (lineNumber - 1));
2791        }
2792
2793}
2794
2795/**
2796 * Map from threads to booleans.
2797 *
2798 * @author James Riely, jriely@cs.depaul.edu, August 2014
2799 */
2800/* private static */class InsideIgnoredMethodMap {
2801        // Stack is probably unnecessary here.  A single boolean would do.
2802        private HashMap<ThreadReference, Stack<Boolean>> map = new HashMap<> ();
2803        public void removeThread (ThreadReference thr) {
2804                map.remove (thr);
2805        }
2806        public void addThread (ThreadReference thr) {
2807                Stack<Boolean> st = new Stack<> ();
2808                st.push (false);
2809                map.put (thr, st);
2810        }
2811        public void enteringIgnoredMethod (ThreadReference thr) {
2812                Stack<Boolean> insideStack = map.get (thr);
2813                insideStack.push (true);        
2814        }
2815        public boolean leavingIgnoredMethod (ThreadReference thr) {
2816                Stack<Boolean> insideStack = map.get (thr);
2817                boolean result = insideStack.peek ();
2818                if (result) insideStack.pop ();
2819                return result;
2820        }
2821        public boolean insideIgnoredMethod (ThreadReference thr) {
2822                return map.get (thr).peek ();
2823        }
2824}
2825
2826/** From sedgewick and wayne */
2827/* private static */class Stack<T> {
2828        private int N;
2829        private Node<T> first;
2830        private static class Node<T> {
2831                T item;
2832                Node<T> next;
2833        }
2834        public Stack () {
2835                first = null;
2836                N = 0;
2837        }
2838        public boolean isEmpty () {
2839                return first == null;
2840        }
2841        public int size () {
2842                return N;
2843        }
2844        public void push (T item) {
2845                Node<T> oldfirst = first;
2846                first = new Node<> ();
2847                first.item = item;
2848                first.next = oldfirst;
2849                N++;
2850        }
2851        public T pop () {
2852                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2853                T item = first.item;
2854                first = first.next;
2855                N--;
2856                return item;
2857        }
2858        public void pop (int n) {
2859                for (int i=n; i>0; i--)
2860                        pop ();
2861        }
2862        public T peek () {
2863                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2864                return first.item;
2865        }
2866}
2867
2868/**
2869 * Keeps track of values in order to spot changes. This keeps copies of stack
2870 * variables (frames) and arrays. Does not store objects, since direct changes
2871 * to fields can be trapped by the JDI.
2872 *
2873 * @author James Riely, jriely@cs.depaul.edu, August 2014
2874 */
2875/* private static */class ValueMap {
2876        private HashMap<ThreadReference, Stack<HashMap<LocalVariable, Value>>> stacks = new HashMap<> ();
2877        private HashMap<ArrayReference, Object[]> arrays = new HashMap<> ();
2878        private HashMap<ArrayReference, Object[]> staticArrays = new HashMap<> ();
2879        private HashMap<ArrayReference, String> staticArrayNames = new HashMap<> ();
2880        private CallTree callTree = new CallTree ();
2881        public int numThreads () {
2882                return stacks.size ();
2883        }
2884        public void clearCallTree () {
2885                callTree = new CallTree ();
2886        }
2887        public void printCallTree () {
2888                callTree.output ();
2889        }
2890        private static class CallTree {
2891                private HashMap<ThreadReference, Stack<String>> frameIdsMap = new HashMap<> ();
2892                private HashMap<ThreadReference, List<String>> gvStringsMap = new HashMap<> ();
2893                private int frameNumber = 0;
2894
2895                public void output () {
2896                        if (!Trace.SHOW_CALL_TREE) return;
2897                        Graphviz.drawStuff ("callTree", (out) -> {
2898                                out.println ("rankdir=LR;");
2899                                for (List<String> gvStrings : gvStringsMap.values ())
2900                                        for (String s : gvStrings) {
2901                                                out.println (s);
2902                                        }
2903                        });
2904                }
2905                public void pop (ThreadReference thr) {
2906                        if (!Trace.SHOW_CALL_TREE) return;
2907                        if (!Trace.drawStepsOfInternal (thr)) return;
2908
2909                        Stack<String> frameIds = frameIdsMap.get(thr);
2910                        if (!frameIds.isEmpty ())
2911                                frameIds.pop ();
2912                }
2913                public void push (StackFrame currFrame, ThreadReference thr) {
2914                        if (!Trace.SHOW_CALL_TREE) return;
2915                        if (!Trace.drawStepsOfInternal (thr)) return;
2916
2917                        Stack<String> frameIds = frameIdsMap.get(thr);
2918                        if (frameIds==null) { frameIds = new Stack<> (); frameIdsMap.put (thr, frameIds); }
2919                        List<String> gvStrings = gvStringsMap.get (thr);
2920                        if (gvStrings==null) { gvStrings = new LinkedList<> (); gvStringsMap.put (thr, gvStrings); }
2921
2922                        String currentFrameId = "f" + frameNumber++;
2923                        StringBuilder sb = new StringBuilder ();
2924                        Method method = currFrame.location ().method ();
2925                        sb.append (currentFrameId);
2926                        sb.append ("[label=\"");
2927                        if (method.isSynthetic ()) sb.append ("!");
2928                        if (!method.isStatic ()) {
2929                                sb.append (Graphviz.quote (Format.valueToStringShort (currFrame.thisObject ())));
2930                                sb.append (":");
2931                        }
2932                        sb.append (Graphviz.quote (Format.methodToString (method, true, false, ".")));
2933                        //sb.append (Graphviz.quote (method.name ()));
2934                        sb.append ("(");
2935                        List<LocalVariable> locals = null;
2936                        try { locals = currFrame.visibleVariables (); } catch (AbsentInformationException e) { }
2937                        if (locals != null) {
2938                                boolean first = true;
2939                                for (LocalVariable l : locals)
2940                                        if (l.isArgument ()) {
2941                                                if (!first) sb.append (", ");
2942                                                else first = false;
2943                                                String valString = Format.valueToString (currFrame.getValue (l));
2944                                                //Value val = currFrame.getValue (l);
2945                                                //String valString = val==null ? "null" : val.toString ();
2946                                                sb.append (Graphviz.quote (valString));
2947                                        }
2948                        }
2949                        sb.append (")\"");
2950                        sb.append (Trace.GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES);
2951                        sb.append ("];");
2952                        gvStrings.add (sb.toString ());
2953                        if (!frameIds.isEmpty ()) {
2954                                gvStrings.add (frameIds.peek () + " -> " + currentFrameId + "[label=\"\"" + Trace.GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES + "];");
2955                        }
2956                        frameIds.push (currentFrameId);
2957                }
2958        }
2959
2960        public boolean maybeAdjustAfterException (ThreadReference thr) {
2961                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2962
2963                // count the number of frames left
2964                int oldCount = stack.size ();
2965                int currentCount = 0;
2966                List<StackFrame> frames;
2967                try {
2968                        frames = thr.frames ();
2969                } catch (IncompatibleThreadStateException e) {
2970                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2971                }
2972
2973                for (StackFrame frame : frames) {
2974                        String calledMethodClassname = frame.location ().declaringType ().name ();
2975                        if (!Format.matchesExcludePrefix (calledMethodClassname)) currentCount++;
2976                }
2977
2978                if (oldCount > currentCount) {
2979                        for (int i = oldCount - currentCount; i > 0; i--) {
2980                                stack.pop ();
2981                                callTree.pop (thr);
2982                        }
2983                        return true;
2984                }
2985                return false;
2986        }
2987        public int numFrames (ThreadReference thr) {
2988                return stacks.get (thr).size ();
2989        }
2990        public void stackCreate (ThreadReference thr) {
2991                stacks.put (thr, new Stack<> ());
2992        }
2993        public void stackDestroy (ThreadReference thr) {
2994                stacks.remove (thr);
2995        }
2996        public void stackPushFrame (StackFrame currFrame, ThreadReference thr) {
2997                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
2998                callTree.push (currFrame, thr);
2999                List<LocalVariable> locals;
3000                try {
3001                        locals = currFrame.visibleVariables ();
3002                } catch (AbsentInformationException e) {
3003                        return;
3004                }
3005
3006                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3007                HashMap<LocalVariable, Value> frame = new HashMap<> ();
3008                stack.push (frame);
3009
3010                for (LocalVariable l : locals) {
3011                        Value v = currFrame.getValue (l);
3012                        frame.put (l, v);
3013                        if (v instanceof ArrayReference) registerArray ((ArrayReference) v);
3014                }
3015        }
3016
3017        public void stackPopFrame (ThreadReference thr) {
3018                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
3019                callTree.pop (thr);
3020                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3021                stack.pop ();
3022                // space leak in arrays HashMap: arrays never removed
3023        }
3024
3025        public void stackUpdateFrame (Method meth, ThreadReference thr, IndentPrinter printer) {
3026                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
3027                StackFrame currFrame = Format.getFrame (meth, thr);
3028                List<LocalVariable> locals;
3029                try {
3030                        locals = currFrame.visibleVariables ();
3031                } catch (AbsentInformationException e) {
3032                        return;
3033                }
3034                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3035                if (stack.isEmpty ()) {
3036                        throw new Error ("\n!!!! Frame empty: " + meth + " : " + thr);
3037                }
3038                HashMap<LocalVariable, Value> frame = stack.peek ();
3039
3040                String debug = Trace.DEBUG ? "#1" : "";
3041                for (LocalVariable l : locals) {
3042                        Value oldValue = frame.get (l);
3043                        Value newValue = currFrame.getValue (l);
3044                        if (valueHasChanged (oldValue, newValue)) {
3045                                frame.put (l, newValue);
3046                                if (newValue instanceof ArrayReference) registerArray ((ArrayReference) newValue);
3047                                String change = (oldValue == null) ? "|" : ">";
3048                                printer.println (thr, "  " + debug + change + " " + l.name () + " = " + Format.valueToString (newValue));
3049                        }
3050                }
3051
3052                ObjectReference thisObj = currFrame.thisObject ();
3053                if (thisObj != null) {
3054                        boolean show = Format.tooManyFields (thisObj);
3055                        if (arrayFieldHasChanged (show, thr, thisObj, printer) && !show) printer.println (thr, "  " + debug + "> this = " + Format.objectToStringLong (thisObj));
3056                }
3057                arrayStaticFieldHasChanged (true, thr, printer);
3058        }
3059
3060        public void registerArray (ArrayReference val) {
3061                if (!arrays.containsKey (val)) {
3062                        arrays.put (val, copyArray (val));
3063                }
3064        }
3065        public boolean registerStaticArray (ArrayReference val, String name) {
3066                if (!staticArrays.containsKey (val)) {
3067                        staticArrays.put (val, copyArray (val));
3068                        staticArrayNames.put (val, name);
3069                        return true;
3070                }
3071                return false;
3072        }
3073        private static Object[] copyArray (ArrayReference oldArrayReference) {
3074                Object[] newArray = new Object[oldArrayReference.length ()];
3075                for (int i = 0; i < newArray.length; i++) {
3076                        Value val = oldArrayReference.getValue (i);
3077                        if (val instanceof ArrayReference) newArray[i] = copyArray ((ArrayReference) val);
3078                        else newArray[i] = val;
3079                }
3080                return newArray;
3081        }
3082
3083        private boolean valueHasChanged (Value oldValue, Value newValue) {
3084                if (oldValue == null && newValue == null) return false;
3085                if (oldValue == null && newValue != null) return true;
3086                if (oldValue != null && newValue == null) return true;
3087                if (!oldValue.equals (newValue)) return true;
3088                if (!(oldValue instanceof ArrayReference)) return false;
3089                return arrayValueHasChanged ((ArrayReference) oldValue, (ArrayReference) newValue);
3090        }
3091        private boolean arrayStaticFieldHasChanged (Boolean show, ThreadReference thr, IndentPrinter printer) {
3092                boolean result = false;
3093                boolean print = false;
3094                String debug = Trace.DEBUG ? "#7" : "";
3095                String change = ">";
3096                for (ArrayReference a : staticArrays.keySet ()) {
3097                        Object[] objArray = staticArrays.get (a);
3098                        if (arrayValueHasChangedHelper (objArray, a)) {
3099                                result = true;
3100                                print = true;
3101                        }
3102                        if (show && print) {
3103                                printer.println (thr, "  " + debug + change + " " + staticArrayNames.get (a) + " = " + Format.valueToString (a));
3104                        }
3105                }
3106                return result;
3107        }
3108        private boolean arrayFieldHasChanged (Boolean show, ThreadReference thr, ObjectReference objRef, IndentPrinter printer) {
3109                ReferenceType type = objRef.referenceType (); // get type (class) of object
3110                List<Field> fields; // use allFields() to include inherited fields
3111                try {
3112                        fields = type.fields ();
3113                } catch (ClassNotPreparedException e) {
3114                        throw new Error (Trace.BAD_ERROR_MESSAGE);
3115                }
3116                boolean result = false;
3117                String debug = Trace.DEBUG ? "#2" : "";
3118                String change = ">";
3119                for (Field f : fields) {
3120                        Boolean print = false;
3121                        Value v = objRef.getValue (f);
3122                        if (!(v instanceof ArrayReference)) continue;
3123                        ArrayReference a = (ArrayReference) v;
3124                        if (!arrays.containsKey (a)) {
3125                                registerArray (a);
3126                                change = "|";
3127                                result = true;
3128                                print = true;
3129                        } else {
3130                                Object[] objArray = arrays.get (a);
3131                                if (arrayValueHasChangedHelper (objArray, a)) {
3132                                        result = true;
3133                                        print = true;
3134                                }
3135                        }
3136                        if (show && print) {
3137                                printer.println (thr, "  " + debug + change + " " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f)));
3138                        }
3139                }
3140                return result;
3141        }
3142        private boolean arrayValueHasChanged (ArrayReference oldArray, ArrayReference newArray) {
3143                if (oldArray.length () != newArray.length ()) return true;
3144                int len = oldArray.length ();
3145                if (!arrays.containsKey (newArray)) {
3146                        return true;
3147                }
3148                Object[] oldObjArray = arrays.get (newArray);
3149                //            if (oldObjArray.length != len)
3150                //                throw new Error (Trace.BAD_ERROR_MESSAGE);
3151                return arrayValueHasChangedHelper (oldObjArray, newArray);
3152        }
3153        private boolean arrayValueHasChangedHelper (Object[] oldObjArray, ArrayReference newArray) {
3154                int len = oldObjArray.length;
3155                boolean hasChanged = false;
3156                for (int i = 0; i < len; i++) {
3157                        Object oldObject = oldObjArray[i];
3158                        Value newVal = newArray.getValue (i);
3159                        if (oldObject == null && newVal != null) {
3160                                oldObjArray[i] = newVal;
3161                                hasChanged = true;
3162                        }
3163                        if (oldObject instanceof Value && valueHasChanged ((Value) oldObject, newVal)) {
3164                                //System.out.println ("BOB:" + i + ":" + oldObject + ":" + newVal);
3165                                oldObjArray[i] = newVal;
3166                                hasChanged = true;
3167                        }
3168                        if (oldObject instanceof Object[]) {
3169                                //if (!(newVal instanceof ArrayReference)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3170                                if (arrayValueHasChangedHelper ((Object[]) oldObject, (ArrayReference) newVal)) {
3171                                        hasChanged = true;
3172                                }
3173                        }
3174                }
3175                return hasChanged;
3176        }
3177}
3178
3179/* private static */class Graphviz {
3180        private Graphviz () {} // noninstantiable class
3181        //- This code is based on LJV:
3182        // LJV.java --- Generate a graph of an object, using Graphviz
3183        // The Lightweight Java Visualizer (LJV)
3184        // https://www.cs.auckland.ac.nz/~j-hamer/
3185
3186        //- Author:     John Hamer <J.Hamer@cs.auckland.ac.nz>
3187        //- Created:    Sat May 10 15:27:48 2003
3188        //- Time-stamp: <2004-08-23 12:47:06 jham005>
3189
3190        //- Copyright (C) 2004  John Hamer, University of Auckland
3191        //-
3192        //-   This program is free software; you can redistribute it and/or
3193        //-   modify it under the terms of the GNU General Public License
3194        //-   as published by the Free Software Foundation; either version 2
3195        //-   of the License, or (at your option) any later version.
3196        //-
3197        //-   This program is distributed in the hope that it will be useful,
3198        //-   but WITHOUT ANY WARRANTY; without even the implied warranty of
3199        //-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3200        //-   GNU General Public License for more details.
3201        //-
3202        //-   You should have received a copy of the GNU General Public License along
3203        //-   with this program; if not, write to the Free Software Foundation, Inc.,
3204        //-   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
3205
3206        //- $Id: LJV.java,v 1.1 2004/07/14 02:03:45 jham005 Exp $
3207        //
3208
3209        /**
3210         * Graphics files are saved in directory dirName/mainClassName.
3211         * dirName directory is created if it does not already exist.
3212         * If dirName/mainClassName exists, then numbers are appended to the directory name:
3213         * "dirName/mainClassName 1", "dirName/mainClassName 2", etc.
3214         */
3215        public static void setOutputDirectory (String dirName, String mainClassName) {
3216                if (dirName == null || mainClassName == null) {
3217                        throw new Error ("\n!!!! no nulls please");
3218                }
3219                Graphviz.dirName = dirName;
3220                Graphviz.mainClassName = mainClassName;
3221        }
3222        private static String dirName;
3223        private static String mainClassName;
3224
3225        public static boolean isWindows () {
3226                String osName = System.getProperty("os.name");
3227                if (osName == null) {
3228                        throw new Error("\n!!! os.name not found");
3229                }
3230                osName = osName.toLowerCase(Locale.ENGLISH);
3231                return osName.contains("windows");
3232        }
3233        public static String getDesktop () {
3234                if (isWindows()) {
3235                        // Supposedly this selects the windows desktop folder:
3236                        return javax.swing.filechooser.FileSystemView.getFileSystemView().getHomeDirectory().toString();
3237                } else {
3238                        return System.getProperty ("user.home") + File.separator + "Desktop";
3239                }
3240        }
3241
3242        /**
3243         * The name of the output file is derived from {@code baseFilename} by
3244         * appending successive integers.
3245         */
3246
3247        public static String peekFilename () {
3248                return String.format ("%03d", nextGraphNumber);
3249        }
3250        private static String nextFilename () {
3251                if (baseFilename == null) setBaseFilename ();           
3252                ++nextGraphNumber;
3253                return baseFilename + peekFilename ();          
3254        }
3255        private static int nextGraphNumber = -1;
3256        private static String baseFilename = null;
3257        private static void setBaseFilename () {
3258                if (dirName == null || mainClassName == null) {
3259                        throw new Error ("\n!!!! no call to setOutputDirectory");
3260                }
3261                // create dir
3262                File dir = new File (dirName);
3263                if (!dir.isAbsolute ()) {
3264                        dirName = getDesktop() + File.separator + dirName;
3265                        dir = new File (dirName);
3266                }
3267                if (dir.exists ()) {
3268                        if (!dir.isDirectory ()) 
3269                                throw new Error ("\n!!!! \"" + dir + "\" is not a directory");
3270                        if (!dir.canWrite ()) 
3271                                throw new Error ("\n!!!! Unable to write directory: \"" + dir + "\"");
3272                } else {
3273                        dir.mkdirs ();
3274                }
3275
3276                // create newDir
3277                String prefix = dirName + File.separator;
3278                String[] mainClassPath = mainClassName.split ("\\.");
3279                mainClassName = mainClassPath[mainClassPath.length-1];
3280                File newDir = new File (prefix + mainClassName);
3281                int suffix = 0;
3282                while (newDir.exists()) { 
3283                        suffix++; 
3284                        newDir = new File(prefix + mainClassName + " " + suffix);
3285                }
3286                newDir.mkdir ();
3287
3288                if (!newDir.isDirectory () || !newDir.canWrite ())
3289                        throw new Error ("Failed setOutputDirectory \"" + newDir + "\"");
3290                baseFilename = newDir + File.separator;
3291                nextGraphNumber = -1;
3292        }
3293        //      /** @deprecated */
3294        //      private static void setOutputFilenamePrefix (String s) {
3295        //              File f = new File (s);
3296        //              String fCanonical;
3297        //              try { 
3298        //                      fCanonical = f.getCanonicalPath ();
3299        //              } catch (IOException e) {
3300        //                      throw new Error ("Failed setBaseFilename \"" + f + "\"");
3301        //              }
3302        //
3303        //              String newBaseFilename;
3304        //              if (f.isDirectory ()) {                         
3305        //                      if (f.canWrite ()) {
3306        //                              newBaseFilename = fCanonical + "/trace-"; 
3307        //                      } else {
3308        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3309        //                      }
3310        //              } else {
3311        //                      File parent = (f == null) ? null : f.getParentFile ();
3312        //                      if (parent == null || parent.canWrite ()) {
3313        //                              newBaseFilename = fCanonical;
3314        //                      } else {
3315        //                              System.err.println ("Cannot open directory \"" + f.getParent () + "\" for writing; using the current directory for graphziv output.");
3316        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3317        //                      }
3318        //              }
3319        //              if (!newBaseFilename.equals (baseFilename)) {
3320        //                      baseFilename = newBaseFilename;
3321        //                      nextGraphNumber = -1;
3322        //              }
3323        //      }
3324
3325        public static final HashMap<String, String> objectAttributeMap = new HashMap<> ();
3326        public static final HashMap<String, String> staticClassAttributeMap = new HashMap<> ();
3327        public static final HashMap<String, String> frameAttributeMap = new HashMap<> ();
3328        public static final HashMap<String, String> fieldAttributeMap = new HashMap<> ();
3329
3330        // ----------------------------------- utilities -----------------------------------------------
3331
3332        private static boolean canTreatAsPrimitive (Value v) {
3333                if (v == null || v instanceof PrimitiveValue) return true;
3334                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && v instanceof StringReference) return true;
3335                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (v.type ())) return true;
3336                return false;
3337        }
3338        private static boolean looksLikePrimitiveArray (ArrayReference obj) {
3339                try {
3340                        if (((ArrayType) obj.type ()).componentType () instanceof PrimitiveType) return true;
3341                } catch (ClassNotLoadedException e) {
3342                        return false;
3343                }
3344
3345                for (int i = 0, len = obj.length (); i < len; i++)
3346                        if (!canTreatAsPrimitive (obj.getValue (i))) return false;
3347                return true;
3348        }
3349        private static boolean canIgnoreObjectField (Field field) {
3350                if (!Format.isObjectField (field)) return true;
3351                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3352                        if (ignoredField.equals (field.name ())) return true;
3353                return false;
3354        }
3355        private static boolean canIgnoreStaticField (Field field) {
3356                if (!Format.isStaticField (field)) return true;
3357                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3358                        if (ignoredField.equals (field.name ())) return true;
3359                return false;
3360        }
3361
3362        //private static final String canAppearUnquotedInLabelChars = " /$&*@#!-+()^%;_[],;.=";
3363        private static boolean canAppearUnquotedInLabel (char c) {
3364                return true;
3365                //return canAppearUnquotedInLabelChars.indexOf (c) != -1 || Character.isLetter (c) || Character.isDigit (c);
3366        }
3367        private static final String quotable = "\\\"<>{}|";
3368        protected static String quote (String s) {
3369                s = unescapeJavaString (s);
3370                StringBuffer sb = new StringBuffer ();
3371                for (int i = 0, n = s.length (); i < n; i++) {
3372                        char c = s.charAt (i);
3373                        if (quotable.indexOf (c) != -1) sb.append ('\\').append (c);
3374                        else if (canAppearUnquotedInLabel (c)) sb.append (c);
3375                        else sb.append ("\\\\u").append (Integer.toHexString (c));
3376                }
3377                return sb.toString ();
3378        }
3379        /**
3380         * Unescapes a string that contains standard Java escape sequences.
3381         * <ul>
3382         * <li><strong>\\b \\f \\n \\r \\t \\" \\'</strong> :
3383         * BS, FF, NL, CR, TAB, double and single quote.</li>
3384         * <li><strong>\\N \\NN \\NNN</strong> : Octal character
3385         * specification (0 - 377, 0x00 - 0xFF).</li>
3386         * <li><strong>\\uNNNN</strong> : Hexadecimal based Unicode character.</li>
3387         * </ul>
3388         *
3389         * @param st
3390         *            A string optionally containing standard java escape sequences.
3391         * @return The translated string.
3392         */
3393        // from http://udojava.com/2013/09/28/unescape-a-string-that-contains-standard-java-escape-sequences/
3394        private static String unescapeJavaString(String st) {
3395                StringBuilder sb = new StringBuilder(st.length());
3396                for (int i = 0; i < st.length(); i++) {
3397                        char ch = st.charAt(i);
3398                        if (ch == '\\') {
3399                                char nextChar = (i == st.length() - 1) ? '\\' : st.charAt(i + 1);
3400                                // Octal escape?
3401                                if (nextChar >= '0' && nextChar <= '7') {
3402                                        String code = "" + nextChar;
3403                                        i++;
3404                                        if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3405                                                code += st.charAt(i + 1);
3406                                                i++;
3407                                                if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3408                                                        code += st.charAt(i + 1);
3409                                                        i++;
3410                                                }
3411                                        }
3412                                        sb.append((char) Integer.parseInt(code, 8));
3413                                        continue;
3414                                }
3415                                switch (nextChar) {
3416                                case '\\': ch = '\\'; break;
3417                                case 'b': ch = '\b'; break;
3418                                case 'f': ch = '\f'; break;
3419                                case 'n': ch = '\n'; break;
3420                                case 'r': ch = '\r'; break;
3421                                case 't': ch = '\t'; break;
3422                                case '\"': ch = '\"'; break;
3423                                case '\'': ch = '\''; break;
3424                                // Hex Unicode: u????
3425                                case 'u':
3426                                        if (i >= st.length() - 5) { ch = 'u'; break; }
3427                                        int code = Integer.parseInt(st.substring (i+2,i+6), 16);
3428                                        sb.append(Character.toChars(code));
3429                                        i += 5;
3430                                        continue;
3431                                }
3432                                i++;
3433                        }
3434                        sb.append(ch);
3435                }
3436                return sb.toString();
3437        }
3438
3439
3440
3441        // ----------------------------------- values -----------------------------------------------
3442
3443        protected static final String PREFIX_UNUSED_LABEL = "_____";
3444        private static final String PREFIX_LABEL = "L";
3445        private static final String PREFIX_ARRAY = "A";
3446        private static final String PREFIX_OBJECT = "N";
3447        private static final String PREFIX_STATIC = "S";
3448        private static final String PREFIX_FRAME = "F";
3449        private static final String PREFIX_RETURN = "returnValue";
3450        private static final String PREFIX_EXCEPTION = "exception";
3451
3452        private static void processPrimitiveArray (ArrayReference obj, PrintWriter out) {
3453                out.print (objectGvName (obj) + "[label=\"");
3454                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3455                for (int i = 0, len = obj.length (); i < len; i++) {
3456                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3457                        Value v = obj.getValue (i);
3458                        if (v != null) processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, "", v, out);
3459                }
3460                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3461        }
3462        private static void processObjectArray (ArrayReference obj, PrintWriter out, Set<ObjectReference> visited) {
3463                out.print (objectGvName (obj) + "[label=\"");
3464                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3465                int len = obj.length ();
3466                for (int i = 0; i < len; i++) {
3467                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3468                        out.print ("<" + PREFIX_ARRAY + i + ">");
3469                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) {
3470                                ObjectReference ref = (ObjectReference) obj.getValue (i);
3471                                out.print (objectNameOnly (ref));
3472                        }
3473                }
3474                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3475                for (int i = 0; i < len; i++) {
3476                        ObjectReference ref = (ObjectReference) obj.getValue (i);
3477                        if (ref == null) continue;
3478                        out.println (objectGvName (obj) + ":" + PREFIX_ARRAY + i + ":c -> " + objectGvName (ref) + "[label=\"" + i + "\"" + Trace.GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES + "];");
3479                        processObject (ref, out, visited);
3480                }
3481        }
3482        private static void processValueStandalone (String gvSource, String arrowAttributes, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3483                if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3484                ObjectReference objRef = (ObjectReference) val;
3485                String GvName = objectGvName (objRef);
3486                if (isNode (objRef.type())) {
3487                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3488                }
3489                out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3490                processObject (objRef, out, visited);
3491        }
3492        private static boolean processValueInline (boolean showNull, String prefix, Value val, PrintWriter out) {
3493                if ((!Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) && (!canTreatAsPrimitive (val))) return false;
3494                if (val == null && !showNull)
3495                        return false;
3496                out.print (prefix);
3497                if (val == null) {
3498                        out.print (quote ("null"));
3499                } else if (val instanceof PrimitiveValue) {
3500                        out.print (quote (val.toString ()));
3501                } else if (Trace.SHOW_STRINGS_AS_PRIMITIVE && val instanceof StringReference) {
3502                        out.print (quote (val.toString ()));
3503                } else if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (val.type ())) {
3504                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3505                } else if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY && val instanceof ObjectReference) {
3506                        out.print (quote (objectNameOnly (val)));
3507                }
3508                return true;
3509        }
3510        // val must be primitive, wrapper or string
3511        private static void processWrapperAsSimple (String gvName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3512                String cabs = null;
3513                out.print (gvName + "[label=\"");
3514                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS && val instanceof ObjectReference) {
3515                        out.print (objectNameOnly (val) + " : ");
3516                }
3517                if (val instanceof PrimitiveValue) {
3518                        out.print (quote (val.toString ()));
3519                } else if (val instanceof StringReference) {
3520                        out.print (quote (val.toString ()));
3521                } else {
3522                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3523                }
3524                out.println ("\"" + Trace.GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3525        }
3526
3527        // ----------------------------------- objects -----------------------------------------------
3528
3529        private static String objectNameOnly (Value val) {
3530                if (val == null) return "null";
3531                if (!(val instanceof ObjectReference)) return ""; 
3532                return "@" + ((ObjectReference)val).uniqueID ();
3533        }
3534        private static String objectName (ObjectReference obj) {
3535                if (obj == null) return "";
3536                String objString = (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) ? "@" + obj.uniqueID () + " : " : "";
3537                return objString + Format.shortenFullyQualifiedName (obj.type ().name ());
3538        }
3539        private static String objectGvName (ObjectReference obj) {
3540                return PREFIX_OBJECT + obj.uniqueID ();
3541        }
3542        private static boolean objectHasPrimitives (List<Field> fs, ObjectReference obj) {
3543                for (Field f : fs) {
3544                        if (canIgnoreObjectField (f)) continue;
3545                        if (canTreatAsPrimitive (obj.getValue (f))) return true;
3546                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3547                }
3548                return false;
3549        }
3550        private static boolean objectHasNonNodeReferences (List<Field> fs, ObjectReference obj) {
3551                for (Field f : fs) {
3552                        if (isNode (f)) continue;
3553                        if (canIgnoreObjectField (f)) continue;
3554                        if (canTreatAsPrimitive (obj.getValue (f))) continue;
3555                        return true;
3556                }
3557                return false;
3558        }
3559        private static void labelObjectWithNoPrimitiveFields (ObjectReference obj, PrintWriter out) {
3560                String cabs = objectAttributeMap.get (obj.type ().name ());
3561                out.println (objectGvName (obj) + "[label=\"" + objectName (obj) + "\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3562        }
3563        private static void labelObjectWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3564                out.print (objectGvName (obj) + "[label=\"" + objectName (obj) + "|{");
3565                String sep = "";
3566                for (Field f : fs) {
3567                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? f.name () + " = " : "";
3568                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3569                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = "|";
3570                        } 
3571                }
3572                String cabs = objectAttributeMap.get (obj.type ().name ());
3573                out.println ("}\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3574        }
3575        private static void labelNodeWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3576                out.print (objectGvName (obj) + "[label=\"");
3577                String sep = "";
3578                for (Field f : fs) {
3579                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_LABELS ? f.name () + " = " : "";
3580                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3581                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = ", ";
3582                        }
3583                }
3584                String cabs = objectAttributeMap.get (obj.type ().name ());
3585                out.println ("\"" + Trace.GRAPHVIZ_NODE_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3586        }
3587        private static int nullId = 0;
3588        private static void processNodeStandalone (boolean srcIsNode, boolean srcHasNonNodeReferences, String gvSource, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3589                String arrowAttributes = Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES;
3590                if (!srcIsNode) {
3591                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3592                }
3593                if (srcIsNode && !srcHasNonNodeReferences && !Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_ARROWS) {
3594                        fieldName = "";
3595                }
3596                if (val == null) {
3597                        String id = "null__" + nullId;
3598                        nullId++;
3599                        out.print (id + "[shape=\"point\"];");                  
3600                        out.println (gvSource + " -> " + id + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3601                } else {
3602                        if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3603                        ObjectReference objRef = (ObjectReference) val;
3604                        String GvName = objectGvName (objRef);
3605                        out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3606                        processObject (objRef, out, visited);
3607                }
3608        }
3609        private static void processObjectWithLabel (String label, ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3610                processObject (obj, out, visited);
3611                if (!label.startsWith (PREFIX_UNUSED_LABEL)) {
3612                        String gvObjName = objectGvName (obj);
3613                        String gvLabelName = PREFIX_LABEL + label;
3614                        out.println (gvLabelName + "[label=\"" + label + "\"" + Trace.GRAPHVIZ_LABEL_BOX_ATTRIBUTES + "];");
3615                        out.println (gvLabelName + " -> " + gvObjName + "[label=\"\"" + Trace.GRAPHVIZ_LABEL_ARROW_ATTRIBUTES + "];");
3616                }
3617        }
3618        private static Value valueByFieldname (ObjectReference obj, String fieldName) {
3619                ReferenceType type = (ReferenceType) obj.type ();
3620                Field field = type.fieldByName (fieldName);
3621                return obj.getValue (field);
3622        }
3623        private static boolean isNode (Type type) {
3624                String typeName = type.name ();
3625                for (String s : Trace.GRAPHVIZ_NODE_CLASS) {
3626                        //System.err.printf ("s=%s, typeName=%s\n", s, typeName);
3627                        if (typeName.endsWith (s)) return true;
3628                }
3629                return false;
3630        }
3631        private static boolean isNode (Field f) {
3632                try {
3633                        return isNode (f.type());
3634                } catch (ClassNotLoadedException e) {
3635                        return false; //throw new Error ("\n!!!! Node class not loaded");
3636                }
3637        }
3638        private static void processObject (ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3639                if (visited.add (obj)) {
3640                        Type type = obj.type ();
3641                        String typeName = type.name ();
3642
3643                        if (!Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && Format.isWrapper (type)) {
3644                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3645                        } else if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && obj instanceof StringReference) {
3646                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3647                        } else if (obj instanceof ArrayReference) {
3648                                ArrayReference arr = (ArrayReference) obj;
3649                                if (looksLikePrimitiveArray (arr)) processPrimitiveArray (arr, out);
3650                                else processObjectArray (arr, out, visited);
3651                        } else {
3652                                List<Field> fs = ((ReferenceType) type).fields ();
3653                                if (isNode (type)) labelNodeWithSomePrimitiveFields (obj, fs, out);
3654                                else if (objectHasPrimitives (fs, obj)) labelObjectWithSomePrimitiveFields (obj, fs, out);
3655                                else labelObjectWithNoPrimitiveFields (obj, out);
3656                                if (!Format.matchesExcludePrefixShow (typeName)) {
3657                                        //System.err.println (typeName);
3658                                        String source = objectGvName (obj);
3659                                        for (Field f : fs) {
3660                                                Value value = obj.getValue (f);
3661                                                if (isNode (f)) {
3662                                                        processNodeStandalone (isNode (type), objectHasNonNodeReferences (fs, obj), source, f.name (), value, out, visited);
3663                                                } else if ((!canIgnoreObjectField (f)) && (!canTreatAsPrimitive (value))) {
3664                                                        processValueStandalone (source, Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES, f.name (), value, out, visited);
3665                                                }
3666                                        }
3667                                }
3668                        }
3669                }
3670        }
3671
3672        // ----------------------------------- static classes -----------------------------------------------
3673
3674        private static String staticClassName (ReferenceType type) {
3675                return Format.shortenFullyQualifiedName (type.name ());
3676        }
3677        private static String staticClassGvName (ReferenceType type) {
3678                return PREFIX_STATIC + type.classObject ().uniqueID ();
3679        }
3680        private static boolean staticClassHasFields (List<Field> fs) {
3681                for (Field f : fs) {
3682                        if (!canIgnoreStaticField (f)) return true;
3683                }
3684                return false;
3685        }
3686        private static boolean staticClassHasPrimitives (List<Field> fs, ReferenceType staticClass) {
3687                for (Field f : fs) {
3688                        if (canIgnoreStaticField (f)) continue;
3689                        if (canTreatAsPrimitive (staticClass.getValue (f))) return true;
3690                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3691                }
3692                return false;
3693        }
3694        private static void labelStaticClassWithNoPrimitiveFields (ReferenceType type, PrintWriter out) {
3695                String cabs = staticClassAttributeMap.get (type.name ());
3696                out.println (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3697        }
3698        private static void labelStaticClassWithSomePrimitiveFields (ReferenceType type, List<Field> fs, PrintWriter out) {
3699                out.print (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "|{");
3700                String sep = "";
3701                for (Field field : fs) {
3702                        if (!canIgnoreStaticField (field)) {
3703                                String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? field.name () + " = " : "";
3704                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, type.getValue (field), out)) sep = "|";
3705                        }
3706                }
3707                String cabs = staticClassAttributeMap.get (type.name ());
3708                out.println ("}\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3709        }
3710        private static void processStaticClass (ReferenceType type, PrintWriter out, Set<ObjectReference> visited) {
3711                String typeName = type.name ();
3712                List<Field> fs = type.fields ();
3713                if (!staticClassHasFields (fs)) {
3714                        return;
3715                }
3716                if (staticClassHasPrimitives (fs, type)) {
3717                        labelStaticClassWithSomePrimitiveFields (type, fs, out);
3718                } else {
3719                        labelStaticClassWithNoPrimitiveFields (type, out);
3720                }
3721                if (!Format.matchesExcludePrefixShow (type.name ())) {
3722                        String source = staticClassGvName (type);
3723                        for (Field f : fs) {
3724                                if (f.isStatic ()) {
3725                                        Value value = type.getValue (f);
3726                                        if ((!canIgnoreStaticField (f)) && (!canTreatAsPrimitive (value))) {
3727                                                String name = f.name ();
3728                                                processValueStandalone (source, Trace.GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES, name, value, out, visited);
3729                                        }
3730                                }
3731                        }
3732                }
3733        }
3734
3735        // ----------------------------------- frames -----------------------------------------------
3736        private static String frameName (int frameNumber, StackFrame frame, Method method, int lineNumber) {
3737                String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + frameNumber + " : " : "";
3738                return objString + Format.methodToString (method, true, false, ".") + " # " + lineNumber;
3739        }
3740        private static String frameGvName (int frameNumber) {
3741                return PREFIX_FRAME + frameNumber;
3742        }
3743        private static boolean frameHasPrimitives (Map<LocalVariable, Value> ls) {
3744                for (LocalVariable lv : ls.keySet ()) {
3745                        Value v = ls.get (lv);
3746                        if (canTreatAsPrimitive (v)) return true;
3747                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3748                }
3749                return false;
3750        }
3751        private static void labelFrameWithNoPrimitiveLocals (int frameNumber, StackFrame frame, PrintWriter out) {
3752                Location location = frame.location ();
3753                ReferenceType type = location.declaringType ();
3754                Method method = location.method ();
3755                String attributes = frameAttributeMap.get (type.name ());
3756                out.println (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES
3757                                + (attributes == null ? "" : "," + attributes) + "];");
3758        }
3759        private static void labelFrameWithSomePrimitiveLocals (int frameNumber, StackFrame frame, Map<LocalVariable, Value> ls, PrintWriter out) {
3760                Location location = frame.location ();
3761                ReferenceType type = location.declaringType ();
3762                Method method = location.method ();
3763                out.print (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "|{");
3764                String sep = "";
3765                for (LocalVariable lv : ls.keySet ()) {
3766                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? lv.name () + " = " : "";
3767                        if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_VARIABLES, sep + name, ls.get (lv), out)) sep = "|";
3768                }
3769                String cabs = frameAttributeMap.get (type.name ());
3770                out.println ("}\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3771        }
3772        private static boolean processFrame (int frameNumber, StackFrame frame, PrintWriter out, Set<ObjectReference> visited) {
3773                Location location = frame.location ();
3774                ReferenceType type = location.declaringType ();
3775                Method method = location.method ();
3776                if (Format.matchesExcludePrefixShow (type.name ())) return false;
3777
3778                Map<LocalVariable, Value> ls;
3779                try {
3780                        ls = frame.getValues (frame.visibleVariables ());
3781                } catch (AbsentInformationException e) {
3782                        return false;
3783                }
3784                if (frameHasPrimitives (ls)) {
3785                        labelFrameWithSomePrimitiveLocals (frameNumber, frame, ls, out);
3786                } else {
3787                        labelFrameWithNoPrimitiveLocals (frameNumber, frame, out);
3788                }
3789                ObjectReference thisObject = frame.thisObject ();
3790                if (thisObject != null) processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "this", thisObject, out, visited);
3791                for (LocalVariable lv : ls.keySet ()) {
3792                        Value value = ls.get (lv);
3793                        if (!canTreatAsPrimitive (value)) {
3794                                processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, lv.name (), value, out, visited);
3795                        }
3796                }
3797                return true;
3798        }
3799
3800        // ----------------------------------- top level -----------------------------------------------
3801
3802        public static void drawFramesCheck (String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses) {
3803                if (Trace.drawStepsOfInternal (frames, returnVal)) {
3804                        int len = 0;
3805                        if (frames!=null) len = (Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME)? 2 : frames.size();
3806                        drawFrames (0, len, loc, returnVal, exnVal, frames, staticClasses, false);
3807                }
3808        }
3809        public static void drawFrames (int start, int len, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, boolean overrideShowStatics) {
3810                drawStuff (loc, (out) -> {
3811                        Set<ObjectReference> visited = new HashSet<> ();
3812                        if ((overrideShowStatics || Trace.GRAPHVIZ_SHOW_STATIC_CLASSES) && staticClasses != null) {
3813                                for (ReferenceType staticClass : staticClasses) {
3814                                        processStaticClass (staticClass, out, visited);
3815                                }
3816                        }
3817                        if (frames != null) {
3818                                for (int i = len - 1, prev = i; i >= start; i--) {
3819                                        StackFrame currentFrame = frames.get (i);
3820                                        Method meth = currentFrame.location ().method ();
3821                                        if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) continue;
3822                                        if (processFrame (len - i, currentFrame, out, visited)) {
3823                                                if (prev != i) {
3824                                                        out.println (frameGvName (len - i) + " -> " + frameGvName (len - prev) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3825                                                        prev = i;
3826                                                }
3827                                        }
3828                                }
3829                                // show the return value -- without this, it mysteriously disappears when drawing all steps
3830                                if (!Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME && returnVal != null && !(returnVal instanceof VoidValue)) {
3831                                        String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + (len + 1) + " : " : "";
3832                                        if (canTreatAsPrimitive (returnVal)) {
3833                                                out.print (PREFIX_RETURN + " [label=\"" + objString + "returnValue = ");
3834                                                processValueInline (true, "", returnVal, out);
3835                                                out.println ("\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3836                                                out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3837                                        } else {
3838                                                out.println (PREFIX_RETURN + " [label=\"" + objString + "returnValue\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3839                                                processValueStandalone (PREFIX_RETURN, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", returnVal, out, visited);
3840                                                out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3841                                        }
3842                                }
3843                        }
3844                        // show the exception value
3845                        if (exnVal != null && !(exnVal instanceof VoidValue)) {
3846                                if (canTreatAsPrimitive (exnVal)) {
3847                                        out.print (PREFIX_EXCEPTION + " [label=\"exception = ");
3848                                        processValueInline (true, "", exnVal, out);
3849                                        out.println ("\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3850                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3851                                } else {
3852                                        out.println (PREFIX_EXCEPTION + " [label=\"exception\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3853                                        processValueStandalone (PREFIX_EXCEPTION, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", exnVal, out, visited);
3854                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3855                                }
3856                        }
3857                });
3858        }
3859        public static void drawObjects (String loc, Map<String, ObjectReference> objects) {
3860                drawStuff (loc, (out) -> {
3861                        Set<ObjectReference> visited = new HashSet<> ();
3862                        for (String key : objects.keySet ()) {
3863                                processObjectWithLabel (key, objects.get (key), out, visited);
3864                        }
3865                });
3866        }
3867        protected static void drawStuff (String loc, Consumer<PrintWriter> consumer) {
3868                String filenamePrefix = nextFilename ();
3869                String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3870                File gvFile = new File (filenamePrefix + theLoc + ".gv");
3871                PrintWriter out;
3872                try {
3873                        out = new PrintWriter (new FileWriter (gvFile));
3874                } catch (IOException e) {
3875                        throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3876                }
3877                out.println ("digraph Java {");
3878                consumer.accept (out);
3879                out.println ("}");
3880                out.close ();
3881                //System.err.println (gvFile);
3882                if (Trace.GRAPHVIZ_RUN_GRAPHVIZ) {
3883                        String executable = null;
3884                        for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3885                                if (new File (s).canExecute ()) executable = s;
3886                        }
3887                        if (executable != null) {
3888                                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_OUTPUT_FORMAT);
3889                                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_OUTPUT_FORMAT);
3890                                pb.redirectInput (gvFile);
3891                                pb.redirectOutput (outFile);
3892                                int result = -1;
3893                                try {
3894                                        result = pb.start ().waitFor ();
3895                                } catch (IOException e) {
3896                                        throw new Error ("\n!!!! Cannot execute " + executable + "\n!!!! Make sure you have installed http://www.graphviz.org/"
3897                                                        + "\n!!!! Check the value of GRAPHVIZ_POSSIBLE_DOT_LOCATIONS in " + Trace.class.getCanonicalName ());
3898                                } catch (InterruptedException e) {
3899                                        throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3900                                }
3901                                if (result == 0) {
3902                                        if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3903                                                gvFile.delete ();
3904                                        }
3905                                } else {
3906                                        outFile.delete ();
3907                                }
3908                        }
3909                }
3910        }
3911
3912        //    public static void drawFrames (int start, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, Map<String, ObjectReference> objects) {
3913        //        String filenamePrefix = nextFilename ();
3914        //        String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3915        //        File gvFile = new File (filenamePrefix + theLoc + ".gv");
3916        //        PrintWriter out;
3917        //        try {
3918        //            out = new PrintWriter (new FileWriter (gvFile));
3919        //        } catch (IOException e) {
3920        //            throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3921        //        }
3922        //        processFrames (start, returnVal, exnVal, frames, staticClasses, objects, out);
3923        //        out.close ();
3924        //        //System.err.println (gvFile);
3925        //        if (Trace.GRAPHVIZ_RUN_DOT) {
3926        //            String executable = null;
3927        //            for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3928        //                if (new File (s).canExecute ())
3929        //                    executable = s;
3930        //            }
3931        //            if (executable != null) {
3932        //                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3933        //                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3934        //                pb.redirectInput (gvFile);
3935        //                pb.redirectOutput(outFile);
3936        //                int result = -1;
3937        //                try {
3938        //                    result = pb.start ().waitFor ();
3939        //                } catch (IOException e) {
3940        //                    throw new Error ("\n!!!! Cannot execute " + executable +
3941        //                            "\n!!!! Make sure you have installed http://www.graphviz.org/" +
3942        //                            "\n!!!! Check the value of GRAPHVIZ_DOT_COMMAND in " + Trace.class.getCanonicalName ());
3943        //                } catch (InterruptedException e) {
3944        //                    throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3945        //                }
3946        //                if (result == 0) {
3947        //                    if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3948        //                        gvFile.delete ();
3949        //                    }
3950        //                } else {
3951        //                    outFile.delete ();
3952        //                }
3953        //            }
3954        //        }
3955        //    }
3956}