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        private static boolean errorFound = false;
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                        if (!errorFound) {
2763                                errorFound = true;
2764                                System.err.println ("\n!!!! Source not found: " + filename);
2765                                System.err.println ("\n !!!   Looking in " + Trace.POSSIBLE_SRC_LOCATIONS);
2766                                System.err.println ("\n !!!   Consider calling Trace.addPossibleSrcLocation(String dirName)");
2767                        }
2768                        return;
2769                }
2770                ArrayList<String> code = new ArrayList<> ();
2771                BufferedReader in = null;
2772                try {
2773                        in = new BufferedReader (new FileReader (srcFilename));
2774                        String line;
2775                        while ((line = in.readLine ()) != null)
2776                                code.add (line);
2777                } catch (IOException ex) {
2778                        System.err.println ("\n!!!! Could not read " + srcFilename);
2779                } finally {
2780                        try {
2781                                if (in != null) in.close ();
2782                        } catch (IOException e) {
2783                                throw new Error ("\n!!!! Problem reading " + srcFilename);
2784                        }
2785                }
2786                listings.put (filename, code);
2787                //println (filename + " added to listings");
2788        }
2789
2790        // return the specified line from filename
2791        public String show (String filename, int lineNumber) {
2792                ArrayList<String> code = listings.get (filename);
2793                if (code == null) return (filename + " not listed");
2794                if ((lineNumber < 1) || (lineNumber > code.size ())) return " [Could not load source file.]";
2795                return (code.get (lineNumber - 1));
2796        }
2797
2798}
2799
2800/**
2801 * Map from threads to booleans.
2802 *
2803 * @author James Riely, jriely@cs.depaul.edu, August 2014
2804 */
2805/* private static */class InsideIgnoredMethodMap {
2806        // Stack is probably unnecessary here.  A single boolean would do.
2807        private HashMap<ThreadReference, Stack<Boolean>> map = new HashMap<> ();
2808        public void removeThread (ThreadReference thr) {
2809                map.remove (thr);
2810        }
2811        public void addThread (ThreadReference thr) {
2812                Stack<Boolean> st = new Stack<> ();
2813                st.push (false);
2814                map.put (thr, st);
2815        }
2816        public void enteringIgnoredMethod (ThreadReference thr) {
2817                Stack<Boolean> insideStack = map.get (thr);
2818                insideStack.push (true);        
2819        }
2820        public boolean leavingIgnoredMethod (ThreadReference thr) {
2821                Stack<Boolean> insideStack = map.get (thr);
2822                boolean result = insideStack.peek ();
2823                if (result) insideStack.pop ();
2824                return result;
2825        }
2826        public boolean insideIgnoredMethod (ThreadReference thr) {
2827                return map.get (thr).peek ();
2828        }
2829}
2830
2831/** From sedgewick and wayne */
2832/* private static */class Stack<T> {
2833        private int N;
2834        private Node<T> first;
2835        private static class Node<T> {
2836                T item;
2837                Node<T> next;
2838        }
2839        public Stack () {
2840                first = null;
2841                N = 0;
2842        }
2843        public boolean isEmpty () {
2844                return first == null;
2845        }
2846        public int size () {
2847                return N;
2848        }
2849        public void push (T item) {
2850                Node<T> oldfirst = first;
2851                first = new Node<> ();
2852                first.item = item;
2853                first.next = oldfirst;
2854                N++;
2855        }
2856        public T pop () {
2857                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2858                T item = first.item;
2859                first = first.next;
2860                N--;
2861                return item;
2862        }
2863        public void pop (int n) {
2864                for (int i=n; i>0; i--)
2865                        pop ();
2866        }
2867        public T peek () {
2868                if (isEmpty ()) throw new NoSuchElementException ("Stack underflow");
2869                return first.item;
2870        }
2871}
2872
2873/**
2874 * Keeps track of values in order to spot changes. This keeps copies of stack
2875 * variables (frames) and arrays. Does not store objects, since direct changes
2876 * to fields can be trapped by the JDI.
2877 *
2878 * @author James Riely, jriely@cs.depaul.edu, August 2014
2879 */
2880/* private static */class ValueMap {
2881        private HashMap<ThreadReference, Stack<HashMap<LocalVariable, Value>>> stacks = new HashMap<> ();
2882        private HashMap<ArrayReference, Object[]> arrays = new HashMap<> ();
2883        private HashMap<ArrayReference, Object[]> staticArrays = new HashMap<> ();
2884        private HashMap<ArrayReference, String> staticArrayNames = new HashMap<> ();
2885        private CallTree callTree = new CallTree ();
2886        public int numThreads () {
2887                return stacks.size ();
2888        }
2889        public void clearCallTree () {
2890                callTree = new CallTree ();
2891        }
2892        public void printCallTree () {
2893                callTree.output ();
2894        }
2895        private static class CallTree {
2896                private HashMap<ThreadReference, Stack<String>> frameIdsMap = new HashMap<> ();
2897                private HashMap<ThreadReference, List<String>> gvStringsMap = new HashMap<> ();
2898                private int frameNumber = 0;
2899
2900                public void output () {
2901                        if (!Trace.SHOW_CALL_TREE) return;
2902                        Graphviz.drawStuff ("callTree", (out) -> {
2903                                out.println ("rankdir=LR;");
2904                                for (List<String> gvStrings : gvStringsMap.values ())
2905                                        for (String s : gvStrings) {
2906                                                out.println (s);
2907                                        }
2908                        });
2909                }
2910                public void pop (ThreadReference thr) {
2911                        if (!Trace.SHOW_CALL_TREE) return;
2912                        if (!Trace.drawStepsOfInternal (thr)) return;
2913
2914                        Stack<String> frameIds = frameIdsMap.get(thr);
2915                        if (!frameIds.isEmpty ())
2916                                frameIds.pop ();
2917                }
2918                public void push (StackFrame currFrame, ThreadReference thr) {
2919                        if (!Trace.SHOW_CALL_TREE) return;
2920                        if (!Trace.drawStepsOfInternal (thr)) return;
2921
2922                        Stack<String> frameIds = frameIdsMap.get(thr);
2923                        if (frameIds==null) { frameIds = new Stack<> (); frameIdsMap.put (thr, frameIds); }
2924                        List<String> gvStrings = gvStringsMap.get (thr);
2925                        if (gvStrings==null) { gvStrings = new LinkedList<> (); gvStringsMap.put (thr, gvStrings); }
2926
2927                        String currentFrameId = "f" + frameNumber++;
2928                        StringBuilder sb = new StringBuilder ();
2929                        Method method = currFrame.location ().method ();
2930                        sb.append (currentFrameId);
2931                        sb.append ("[label=\"");
2932                        if (method.isSynthetic ()) sb.append ("!");
2933                        if (!method.isStatic ()) {
2934                                sb.append (Graphviz.quote (Format.valueToStringShort (currFrame.thisObject ())));
2935                                sb.append (":");
2936                        }
2937                        sb.append (Graphviz.quote (Format.methodToString (method, true, false, ".")));
2938                        //sb.append (Graphviz.quote (method.name ()));
2939                        sb.append ("(");
2940                        List<LocalVariable> locals = null;
2941                        try { locals = currFrame.visibleVariables (); } catch (AbsentInformationException e) { }
2942                        if (locals != null) {
2943                                boolean first = true;
2944                                for (LocalVariable l : locals)
2945                                        if (l.isArgument ()) {
2946                                                if (!first) sb.append (", ");
2947                                                else first = false;
2948                                                String valString = Format.valueToString (currFrame.getValue (l));
2949                                                //Value val = currFrame.getValue (l);
2950                                                //String valString = val==null ? "null" : val.toString ();
2951                                                sb.append (Graphviz.quote (valString));
2952                                        }
2953                        }
2954                        sb.append (")\"");
2955                        sb.append (Trace.GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES);
2956                        sb.append ("];");
2957                        gvStrings.add (sb.toString ());
2958                        if (!frameIds.isEmpty ()) {
2959                                gvStrings.add (frameIds.peek () + " -> " + currentFrameId + "[label=\"\"" + Trace.GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES + "];");
2960                        }
2961                        frameIds.push (currentFrameId);
2962                }
2963        }
2964
2965        public boolean maybeAdjustAfterException (ThreadReference thr) {
2966                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
2967
2968                // count the number of frames left
2969                int oldCount = stack.size ();
2970                int currentCount = 0;
2971                List<StackFrame> frames;
2972                try {
2973                        frames = thr.frames ();
2974                } catch (IncompatibleThreadStateException e) {
2975                        throw new Error (Trace.BAD_ERROR_MESSAGE);
2976                }
2977
2978                for (StackFrame frame : frames) {
2979                        String calledMethodClassname = frame.location ().declaringType ().name ();
2980                        if (!Format.matchesExcludePrefix (calledMethodClassname)) currentCount++;
2981                }
2982
2983                if (oldCount > currentCount) {
2984                        for (int i = oldCount - currentCount; i > 0; i--) {
2985                                stack.pop ();
2986                                callTree.pop (thr);
2987                        }
2988                        return true;
2989                }
2990                return false;
2991        }
2992        public int numFrames (ThreadReference thr) {
2993                return stacks.get (thr).size ();
2994        }
2995        public void stackCreate (ThreadReference thr) {
2996                stacks.put (thr, new Stack<> ());
2997        }
2998        public void stackDestroy (ThreadReference thr) {
2999                stacks.remove (thr);
3000        }
3001        public void stackPushFrame (StackFrame currFrame, ThreadReference thr) {
3002                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
3003                callTree.push (currFrame, thr);
3004                List<LocalVariable> locals;
3005                try {
3006                        locals = currFrame.visibleVariables ();
3007                } catch (AbsentInformationException e) {
3008                        return;
3009                }
3010
3011                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3012                HashMap<LocalVariable, Value> frame = new HashMap<> ();
3013                stack.push (frame);
3014
3015                for (LocalVariable l : locals) {
3016                        Value v = currFrame.getValue (l);
3017                        frame.put (l, v);
3018                        if (v instanceof ArrayReference) registerArray ((ArrayReference) v);
3019                }
3020        }
3021
3022        public void stackPopFrame (ThreadReference thr) {
3023                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
3024                callTree.pop (thr);
3025                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3026                stack.pop ();
3027                // space leak in arrays HashMap: arrays never removed
3028        }
3029
3030        public void stackUpdateFrame (Method meth, ThreadReference thr, IndentPrinter printer) {
3031                if (!Trace.CONSOLE_SHOW_VARIABLES) return;
3032                StackFrame currFrame = Format.getFrame (meth, thr);
3033                List<LocalVariable> locals;
3034                try {
3035                        locals = currFrame.visibleVariables ();
3036                } catch (AbsentInformationException e) {
3037                        return;
3038                }
3039                Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr);
3040                if (stack.isEmpty ()) {
3041                        throw new Error ("\n!!!! Frame empty: " + meth + " : " + thr);
3042                }
3043                HashMap<LocalVariable, Value> frame = stack.peek ();
3044
3045                String debug = Trace.DEBUG ? "#1" : "";
3046                for (LocalVariable l : locals) {
3047                        Value oldValue = frame.get (l);
3048                        Value newValue = currFrame.getValue (l);
3049                        if (valueHasChanged (oldValue, newValue)) {
3050                                frame.put (l, newValue);
3051                                if (newValue instanceof ArrayReference) registerArray ((ArrayReference) newValue);
3052                                String change = (oldValue == null) ? "|" : ">";
3053                                printer.println (thr, "  " + debug + change + " " + l.name () + " = " + Format.valueToString (newValue));
3054                        }
3055                }
3056
3057                ObjectReference thisObj = currFrame.thisObject ();
3058                if (thisObj != null) {
3059                        boolean show = Format.tooManyFields (thisObj);
3060                        if (arrayFieldHasChanged (show, thr, thisObj, printer) && !show) printer.println (thr, "  " + debug + "> this = " + Format.objectToStringLong (thisObj));
3061                }
3062                arrayStaticFieldHasChanged (true, thr, printer);
3063        }
3064
3065        public void registerArray (ArrayReference val) {
3066                if (!arrays.containsKey (val)) {
3067                        arrays.put (val, copyArray (val));
3068                }
3069        }
3070        public boolean registerStaticArray (ArrayReference val, String name) {
3071                if (!staticArrays.containsKey (val)) {
3072                        staticArrays.put (val, copyArray (val));
3073                        staticArrayNames.put (val, name);
3074                        return true;
3075                }
3076                return false;
3077        }
3078        private static Object[] copyArray (ArrayReference oldArrayReference) {
3079                Object[] newArray = new Object[oldArrayReference.length ()];
3080                for (int i = 0; i < newArray.length; i++) {
3081                        Value val = oldArrayReference.getValue (i);
3082                        if (val instanceof ArrayReference) newArray[i] = copyArray ((ArrayReference) val);
3083                        else newArray[i] = val;
3084                }
3085                return newArray;
3086        }
3087
3088        private boolean valueHasChanged (Value oldValue, Value newValue) {
3089                if (oldValue == null && newValue == null) return false;
3090                if (oldValue == null && newValue != null) return true;
3091                if (oldValue != null && newValue == null) return true;
3092                if (!oldValue.equals (newValue)) return true;
3093                if (!(oldValue instanceof ArrayReference)) return false;
3094                return arrayValueHasChanged ((ArrayReference) oldValue, (ArrayReference) newValue);
3095        }
3096        private boolean arrayStaticFieldHasChanged (Boolean show, ThreadReference thr, IndentPrinter printer) {
3097                boolean result = false;
3098                boolean print = false;
3099                String debug = Trace.DEBUG ? "#7" : "";
3100                String change = ">";
3101                for (ArrayReference a : staticArrays.keySet ()) {
3102                        Object[] objArray = staticArrays.get (a);
3103                        if (arrayValueHasChangedHelper (objArray, a)) {
3104                                result = true;
3105                                print = true;
3106                        }
3107                        if (show && print) {
3108                                printer.println (thr, "  " + debug + change + " " + staticArrayNames.get (a) + " = " + Format.valueToString (a));
3109                        }
3110                }
3111                return result;
3112        }
3113        private boolean arrayFieldHasChanged (Boolean show, ThreadReference thr, ObjectReference objRef, IndentPrinter printer) {
3114                ReferenceType type = objRef.referenceType (); // get type (class) of object
3115                List<Field> fields; // use allFields() to include inherited fields
3116                try {
3117                        fields = type.fields ();
3118                } catch (ClassNotPreparedException e) {
3119                        throw new Error (Trace.BAD_ERROR_MESSAGE);
3120                }
3121                boolean result = false;
3122                String debug = Trace.DEBUG ? "#2" : "";
3123                String change = ">";
3124                for (Field f : fields) {
3125                        Boolean print = false;
3126                        Value v = objRef.getValue (f);
3127                        if (!(v instanceof ArrayReference)) continue;
3128                        ArrayReference a = (ArrayReference) v;
3129                        if (!arrays.containsKey (a)) {
3130                                registerArray (a);
3131                                change = "|";
3132                                result = true;
3133                                print = true;
3134                        } else {
3135                                Object[] objArray = arrays.get (a);
3136                                if (arrayValueHasChangedHelper (objArray, a)) {
3137                                        result = true;
3138                                        print = true;
3139                                }
3140                        }
3141                        if (show && print) {
3142                                printer.println (thr, "  " + debug + change + " " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f)));
3143                        }
3144                }
3145                return result;
3146        }
3147        private boolean arrayValueHasChanged (ArrayReference oldArray, ArrayReference newArray) {
3148                if (oldArray.length () != newArray.length ()) return true;
3149                int len = oldArray.length ();
3150                if (!arrays.containsKey (newArray)) {
3151                        return true;
3152                }
3153                Object[] oldObjArray = arrays.get (newArray);
3154                //            if (oldObjArray.length != len)
3155                //                throw new Error (Trace.BAD_ERROR_MESSAGE);
3156                return arrayValueHasChangedHelper (oldObjArray, newArray);
3157        }
3158        private boolean arrayValueHasChangedHelper (Object[] oldObjArray, ArrayReference newArray) {
3159                int len = oldObjArray.length;
3160                boolean hasChanged = false;
3161                for (int i = 0; i < len; i++) {
3162                        Object oldObject = oldObjArray[i];
3163                        Value newVal = newArray.getValue (i);
3164                        if (oldObject == null && newVal != null) {
3165                                oldObjArray[i] = newVal;
3166                                hasChanged = true;
3167                        }
3168                        if (oldObject instanceof Value && valueHasChanged ((Value) oldObject, newVal)) {
3169                                //System.out.println ("BOB:" + i + ":" + oldObject + ":" + newVal);
3170                                oldObjArray[i] = newVal;
3171                                hasChanged = true;
3172                        }
3173                        if (oldObject instanceof Object[]) {
3174                                //if (!(newVal instanceof ArrayReference)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3175                                if (arrayValueHasChangedHelper ((Object[]) oldObject, (ArrayReference) newVal)) {
3176                                        hasChanged = true;
3177                                }
3178                        }
3179                }
3180                return hasChanged;
3181        }
3182}
3183
3184/* private static */class Graphviz {
3185        private Graphviz () {} // noninstantiable class
3186        //- This code is based on LJV:
3187        // LJV.java --- Generate a graph of an object, using Graphviz
3188        // The Lightweight Java Visualizer (LJV)
3189        // https://www.cs.auckland.ac.nz/~j-hamer/
3190
3191        //- Author:     John Hamer <J.Hamer@cs.auckland.ac.nz>
3192        //- Created:    Sat May 10 15:27:48 2003
3193        //- Time-stamp: <2004-08-23 12:47:06 jham005>
3194
3195        //- Copyright (C) 2004  John Hamer, University of Auckland
3196        //-
3197        //-   This program is free software; you can redistribute it and/or
3198        //-   modify it under the terms of the GNU General Public License
3199        //-   as published by the Free Software Foundation; either version 2
3200        //-   of the License, or (at your option) any later version.
3201        //-
3202        //-   This program is distributed in the hope that it will be useful,
3203        //-   but WITHOUT ANY WARRANTY; without even the implied warranty of
3204        //-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3205        //-   GNU General Public License for more details.
3206        //-
3207        //-   You should have received a copy of the GNU General Public License along
3208        //-   with this program; if not, write to the Free Software Foundation, Inc.,
3209        //-   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
3210
3211        //- $Id: LJV.java,v 1.1 2004/07/14 02:03:45 jham005 Exp $
3212        //
3213
3214        /**
3215         * Graphics files are saved in directory dirName/mainClassName.
3216         * dirName directory is created if it does not already exist.
3217         * If dirName/mainClassName exists, then numbers are appended to the directory name:
3218         * "dirName/mainClassName 1", "dirName/mainClassName 2", etc.
3219         */
3220        public static void setOutputDirectory (String dirName, String mainClassName) {
3221                if (dirName == null || mainClassName == null) {
3222                        throw new Error ("\n!!!! no nulls please");
3223                }
3224                Graphviz.dirName = dirName;
3225                Graphviz.mainClassName = mainClassName;
3226        }
3227        private static String dirName;
3228        private static String mainClassName;
3229
3230        public static boolean isWindows () {
3231                String osName = System.getProperty("os.name");
3232                if (osName == null) {
3233                        throw new Error("\n!!! os.name not found");
3234                }
3235                osName = osName.toLowerCase(Locale.ENGLISH);
3236                return osName.contains("windows");
3237        }
3238        public static String getDesktop () {
3239                if (isWindows()) {
3240                        // Supposedly this selects the windows desktop folder:
3241                        return javax.swing.filechooser.FileSystemView.getFileSystemView().getHomeDirectory().toString();
3242                } else {
3243                        return System.getProperty ("user.home") + File.separator + "Desktop";
3244                }
3245        }
3246
3247        /**
3248         * The name of the output file is derived from {@code baseFilename} by
3249         * appending successive integers.
3250         */
3251
3252        public static String peekFilename () {
3253                return String.format ("%03d", nextGraphNumber);
3254        }
3255        private static String nextFilename () {
3256                if (baseFilename == null) setBaseFilename ();           
3257                ++nextGraphNumber;
3258                return baseFilename + peekFilename ();          
3259        }
3260        private static int nextGraphNumber = -1;
3261        private static String baseFilename = null;
3262        private static void setBaseFilename () {
3263                if (dirName == null || mainClassName == null) {
3264                        throw new Error ("\n!!!! no call to setOutputDirectory");
3265                }
3266                // create dir
3267                File dir = new File (dirName);
3268                if (!dir.isAbsolute ()) {
3269                        dirName = getDesktop() + File.separator + dirName;
3270                        dir = new File (dirName);
3271                }
3272                if (dir.exists ()) {
3273                        if (!dir.isDirectory ()) 
3274                                throw new Error ("\n!!!! \"" + dir + "\" is not a directory");
3275                        if (!dir.canWrite ()) 
3276                                throw new Error ("\n!!!! Unable to write directory: \"" + dir + "\"");
3277                } else {
3278                        dir.mkdirs ();
3279                }
3280
3281                // create newDir
3282                String prefix = dirName + File.separator;
3283                String[] mainClassPath = mainClassName.split ("\\.");
3284                mainClassName = mainClassPath[mainClassPath.length-1];
3285                File newDir = new File (prefix + mainClassName);
3286                int suffix = 0;
3287                while (newDir.exists()) { 
3288                        suffix++; 
3289                        newDir = new File(prefix + mainClassName + " " + suffix);
3290                }
3291                newDir.mkdir ();
3292
3293                if (!newDir.isDirectory () || !newDir.canWrite ())
3294                        throw new Error ("Failed setOutputDirectory \"" + newDir + "\"");
3295                baseFilename = newDir + File.separator;
3296                nextGraphNumber = -1;
3297        }
3298        //      /** @deprecated */
3299        //      private static void setOutputFilenamePrefix (String s) {
3300        //              File f = new File (s);
3301        //              String fCanonical;
3302        //              try { 
3303        //                      fCanonical = f.getCanonicalPath ();
3304        //              } catch (IOException e) {
3305        //                      throw new Error ("Failed setBaseFilename \"" + f + "\"");
3306        //              }
3307        //
3308        //              String newBaseFilename;
3309        //              if (f.isDirectory ()) {                         
3310        //                      if (f.canWrite ()) {
3311        //                              newBaseFilename = fCanonical + "/trace-"; 
3312        //                      } else {
3313        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3314        //                      }
3315        //              } else {
3316        //                      File parent = (f == null) ? null : f.getParentFile ();
3317        //                      if (parent == null || parent.canWrite ()) {
3318        //                              newBaseFilename = fCanonical;
3319        //                      } else {
3320        //                              System.err.println ("Cannot open directory \"" + f.getParent () + "\" for writing; using the current directory for graphziv output.");
3321        //                              throw new Error ("Failed setBaseFilename \"" + f + "\"");
3322        //                      }
3323        //              }
3324        //              if (!newBaseFilename.equals (baseFilename)) {
3325        //                      baseFilename = newBaseFilename;
3326        //                      nextGraphNumber = -1;
3327        //              }
3328        //      }
3329
3330        public static final HashMap<String, String> objectAttributeMap = new HashMap<> ();
3331        public static final HashMap<String, String> staticClassAttributeMap = new HashMap<> ();
3332        public static final HashMap<String, String> frameAttributeMap = new HashMap<> ();
3333        public static final HashMap<String, String> fieldAttributeMap = new HashMap<> ();
3334
3335        // ----------------------------------- utilities -----------------------------------------------
3336
3337        private static boolean canTreatAsPrimitive (Value v) {
3338                if (v == null || v instanceof PrimitiveValue) return true;
3339                if (Trace.SHOW_STRINGS_AS_PRIMITIVE && v instanceof StringReference) return true;
3340                if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (v.type ())) return true;
3341                return false;
3342        }
3343        private static boolean looksLikePrimitiveArray (ArrayReference obj) {
3344                try {
3345                        if (((ArrayType) obj.type ()).componentType () instanceof PrimitiveType) return true;
3346                } catch (ClassNotLoadedException e) {
3347                        return false;
3348                }
3349
3350                for (int i = 0, len = obj.length (); i < len; i++)
3351                        if (!canTreatAsPrimitive (obj.getValue (i))) return false;
3352                return true;
3353        }
3354        private static boolean canIgnoreObjectField (Field field) {
3355                if (!Format.isObjectField (field)) return true;
3356                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3357                        if (ignoredField.equals (field.name ())) return true;
3358                return false;
3359        }
3360        private static boolean canIgnoreStaticField (Field field) {
3361                if (!Format.isStaticField (field)) return true;
3362                for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS)
3363                        if (ignoredField.equals (field.name ())) return true;
3364                return false;
3365        }
3366
3367        //private static final String canAppearUnquotedInLabelChars = " /$&*@#!-+()^%;_[],;.=";
3368        private static boolean canAppearUnquotedInLabel (char c) {
3369                return true;
3370                //return canAppearUnquotedInLabelChars.indexOf (c) != -1 || Character.isLetter (c) || Character.isDigit (c);
3371        }
3372        private static final String quotable = "\\\"<>{}|";
3373        protected static String quote (String s) {
3374                s = unescapeJavaString (s);
3375                StringBuffer sb = new StringBuffer ();
3376                for (int i = 0, n = s.length (); i < n; i++) {
3377                        char c = s.charAt (i);
3378                        if (quotable.indexOf (c) != -1) sb.append ('\\').append (c);
3379                        else if (canAppearUnquotedInLabel (c)) sb.append (c);
3380                        else sb.append ("\\\\u").append (Integer.toHexString (c));
3381                }
3382                return sb.toString ();
3383        }
3384        /**
3385         * Unescapes a string that contains standard Java escape sequences.
3386         * <ul>
3387         * <li><strong>\\b \\f \\n \\r \\t \\" \\'</strong> :
3388         * BS, FF, NL, CR, TAB, double and single quote.</li>
3389         * <li><strong>\\N \\NN \\NNN</strong> : Octal character
3390         * specification (0 - 377, 0x00 - 0xFF).</li>
3391         * <li><strong>\\uNNNN</strong> : Hexadecimal based Unicode character.</li>
3392         * </ul>
3393         *
3394         * @param st
3395         *            A string optionally containing standard java escape sequences.
3396         * @return The translated string.
3397         */
3398        // from http://udojava.com/2013/09/28/unescape-a-string-that-contains-standard-java-escape-sequences/
3399        private static String unescapeJavaString(String st) {
3400                StringBuilder sb = new StringBuilder(st.length());
3401                for (int i = 0; i < st.length(); i++) {
3402                        char ch = st.charAt(i);
3403                        if (ch == '\\') {
3404                                char nextChar = (i == st.length() - 1) ? '\\' : st.charAt(i + 1);
3405                                // Octal escape?
3406                                if (nextChar >= '0' && nextChar <= '7') {
3407                                        String code = "" + nextChar;
3408                                        i++;
3409                                        if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3410                                                code += st.charAt(i + 1);
3411                                                i++;
3412                                                if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') {
3413                                                        code += st.charAt(i + 1);
3414                                                        i++;
3415                                                }
3416                                        }
3417                                        sb.append((char) Integer.parseInt(code, 8));
3418                                        continue;
3419                                }
3420                                switch (nextChar) {
3421                                case '\\': ch = '\\'; break;
3422                                case 'b': ch = '\b'; break;
3423                                case 'f': ch = '\f'; break;
3424                                case 'n': ch = '\n'; break;
3425                                case 'r': ch = '\r'; break;
3426                                case 't': ch = '\t'; break;
3427                                case '\"': ch = '\"'; break;
3428                                case '\'': ch = '\''; break;
3429                                // Hex Unicode: u????
3430                                case 'u':
3431                                        if (i >= st.length() - 5) { ch = 'u'; break; }
3432                                        int code = Integer.parseInt(st.substring (i+2,i+6), 16);
3433                                        sb.append(Character.toChars(code));
3434                                        i += 5;
3435                                        continue;
3436                                }
3437                                i++;
3438                        }
3439                        sb.append(ch);
3440                }
3441                return sb.toString();
3442        }
3443
3444
3445
3446        // ----------------------------------- values -----------------------------------------------
3447
3448        protected static final String PREFIX_UNUSED_LABEL = "_____";
3449        private static final String PREFIX_LABEL = "L";
3450        private static final String PREFIX_ARRAY = "A";
3451        private static final String PREFIX_OBJECT = "N";
3452        private static final String PREFIX_STATIC = "S";
3453        private static final String PREFIX_FRAME = "F";
3454        private static final String PREFIX_RETURN = "returnValue";
3455        private static final String PREFIX_EXCEPTION = "exception";
3456
3457        private static void processPrimitiveArray (ArrayReference obj, PrintWriter out) {
3458                out.print (objectGvName (obj) + "[label=\"");
3459                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3460                for (int i = 0, len = obj.length (); i < len; i++) {
3461                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3462                        Value v = obj.getValue (i);
3463                        if (v != null) processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, "", v, out);
3464                }
3465                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3466        }
3467        private static void processObjectArray (ArrayReference obj, PrintWriter out, Set<ObjectReference> visited) {
3468                out.print (objectGvName (obj) + "[label=\"");
3469                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) out.print (objectName (obj));
3470                int len = obj.length ();
3471                for (int i = 0; i < len; i++) {
3472                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS || i != 0) out.print ("|");
3473                        out.print ("<" + PREFIX_ARRAY + i + ">");
3474                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) {
3475                                ObjectReference ref = (ObjectReference) obj.getValue (i);
3476                                out.print (objectNameOnly (ref));
3477                        }
3478                }
3479                out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];");
3480                for (int i = 0; i < len; i++) {
3481                        ObjectReference ref = (ObjectReference) obj.getValue (i);
3482                        if (ref == null) continue;
3483                        out.println (objectGvName (obj) + ":" + PREFIX_ARRAY + i + ":c -> " + objectGvName (ref) + "[label=\"" + i + "\"" + Trace.GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES + "];");
3484                        processObject (ref, out, visited);
3485                }
3486        }
3487        private static void processValueStandalone (String gvSource, String arrowAttributes, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3488                if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3489                ObjectReference objRef = (ObjectReference) val;
3490                String GvName = objectGvName (objRef);
3491                if (isNode (objRef.type())) {
3492                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3493                }
3494                out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3495                processObject (objRef, out, visited);
3496        }
3497        private static boolean processValueInline (boolean showNull, String prefix, Value val, PrintWriter out) {
3498                if ((!Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) && (!canTreatAsPrimitive (val))) return false;
3499                if (val == null && !showNull)
3500                        return false;
3501                out.print (prefix);
3502                if (val == null) {
3503                        out.print (quote ("null"));
3504                } else if (val instanceof PrimitiveValue) {
3505                        out.print (quote (val.toString ()));
3506                } else if (Trace.SHOW_STRINGS_AS_PRIMITIVE && val instanceof StringReference) {
3507                        out.print (quote (val.toString ()));
3508                } else if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (val.type ())) {
3509                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3510                } else if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY && val instanceof ObjectReference) {
3511                        out.print (quote (objectNameOnly (val)));
3512                }
3513                return true;
3514        }
3515        // val must be primitive, wrapper or string
3516        private static void processWrapperAsSimple (String gvName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3517                String cabs = null;
3518                out.print (gvName + "[label=\"");
3519                if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS && val instanceof ObjectReference) {
3520                        out.print (objectNameOnly (val) + " : ");
3521                }
3522                if (val instanceof PrimitiveValue) {
3523                        out.print (quote (val.toString ()));
3524                } else if (val instanceof StringReference) {
3525                        out.print (quote (val.toString ()));
3526                } else {
3527                        out.print (quote (Format.wrapperToString ((ObjectReference) val)));
3528                }
3529                out.println ("\"" + Trace.GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3530        }
3531
3532        // ----------------------------------- objects -----------------------------------------------
3533
3534        private static String objectNameOnly (Value val) {
3535                if (val == null) return "null";
3536                if (!(val instanceof ObjectReference)) return ""; 
3537                return "@" + ((ObjectReference)val).uniqueID ();
3538        }
3539        private static String objectName (ObjectReference obj) {
3540                if (obj == null) return "";
3541                String objString = (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) ? "@" + obj.uniqueID () + " : " : "";
3542                return objString + Format.shortenFullyQualifiedName (obj.type ().name ());
3543        }
3544        private static String objectGvName (ObjectReference obj) {
3545                return PREFIX_OBJECT + obj.uniqueID ();
3546        }
3547        private static boolean objectHasPrimitives (List<Field> fs, ObjectReference obj) {
3548                for (Field f : fs) {
3549                        if (canIgnoreObjectField (f)) continue;
3550                        if (canTreatAsPrimitive (obj.getValue (f))) return true;
3551                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3552                }
3553                return false;
3554        }
3555        private static boolean objectHasNonNodeReferences (List<Field> fs, ObjectReference obj) {
3556                for (Field f : fs) {
3557                        if (isNode (f)) continue;
3558                        if (canIgnoreObjectField (f)) continue;
3559                        if (canTreatAsPrimitive (obj.getValue (f))) continue;
3560                        return true;
3561                }
3562                return false;
3563        }
3564        private static void labelObjectWithNoPrimitiveFields (ObjectReference obj, PrintWriter out) {
3565                String cabs = objectAttributeMap.get (obj.type ().name ());
3566                out.println (objectGvName (obj) + "[label=\"" + objectName (obj) + "\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3567        }
3568        private static void labelObjectWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3569                out.print (objectGvName (obj) + "[label=\"" + objectName (obj) + "|{");
3570                String sep = "";
3571                for (Field f : fs) {
3572                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? f.name () + " = " : "";
3573                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3574                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = "|";
3575                        } 
3576                }
3577                String cabs = objectAttributeMap.get (obj.type ().name ());
3578                out.println ("}\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3579        }
3580        private static void labelNodeWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) {
3581                out.print (objectGvName (obj) + "[label=\"");
3582                String sep = "";
3583                for (Field f : fs) {
3584                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_LABELS ? f.name () + " = " : "";
3585                        if (!isNode (f) && !canIgnoreObjectField (f)) {
3586                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (f), out)) sep = ", ";
3587                        }
3588                }
3589                String cabs = objectAttributeMap.get (obj.type ().name ());
3590                out.println ("\"" + Trace.GRAPHVIZ_NODE_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3591        }
3592        private static void processNodeStandalone (boolean srcIsNode, boolean srcHasNonNodeReferences, String gvSource, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) {
3593                String arrowAttributes = Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES;
3594                if (!srcIsNode) {
3595                        arrowAttributes = arrowAttributes + Trace.GRAPHVIZ_NODE_ARROW_ATTRIBUTES;
3596                }
3597                if (srcIsNode && !srcHasNonNodeReferences && !Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_NODE_ARROWS) {
3598                        fieldName = "";
3599                }
3600                if (val == null) {
3601                        addNullDot(gvSource, fieldName, arrowAttributes, out);
3602                } else {
3603                        if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE);
3604                        ObjectReference objRef = (ObjectReference) val;
3605                        String GvName = objectGvName (objRef);
3606                        out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];");
3607                        processObject (objRef, out, visited);
3608                }
3609        }
3610        private static int nullId = 0;
3611        private static void addNullDot (String gvSource, String label, String arrowAttributes, PrintWriter out) {
3612                String id = "null__" + nullId;
3613                nullId++;
3614                out.print (id + "[shape=\"point\"];");                  
3615                out.println (gvSource + " -> " + id + "[label=\"" + label + "\"" + arrowAttributes + "];");
3616        }
3617        private static void processObjectWithLabel (String label, ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3618                processObject (obj, out, visited);
3619                if (!label.startsWith (PREFIX_UNUSED_LABEL)) {
3620                        String gvObjName = objectGvName (obj);
3621                        String gvLabelName = PREFIX_LABEL + label;
3622                        out.println (gvLabelName + "[label=\"" + label + "\"" + Trace.GRAPHVIZ_LABEL_BOX_ATTRIBUTES + "];");
3623                        out.println (gvLabelName + " -> " + gvObjName + "[label=\"\"" + Trace.GRAPHVIZ_LABEL_ARROW_ATTRIBUTES + "];");
3624                }
3625        }
3626        private static Value valueByFieldname (ObjectReference obj, String fieldName) {
3627                ReferenceType type = (ReferenceType) obj.type ();
3628                Field field = type.fieldByName (fieldName);
3629                return obj.getValue (field);
3630        }
3631        private static boolean isNode (Type type) {
3632                String typeName = type.name ();
3633                for (String s : Trace.GRAPHVIZ_NODE_CLASS) {
3634                        //System.err.printf ("s=%s, typeName=%s\n", s, typeName);
3635                        if (typeName.endsWith (s)) return true;
3636                }
3637                return false;
3638        }
3639        private static boolean isNode (Field f) {
3640                try {
3641                        return isNode (f.type());
3642                } catch (ClassNotLoadedException e) {
3643                        return false; //throw new Error ("\n!!!! Node class not loaded");
3644                }
3645        }
3646        private static void processObject (ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) {
3647                if (visited.add (obj)) {
3648                        Type type = obj.type ();
3649                        String typeName = type.name ();
3650
3651                        if (!Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && Format.isWrapper (type)) {
3652                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3653                        } else if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && obj instanceof StringReference) {
3654                                processWrapperAsSimple (objectGvName (obj), obj, out, visited);
3655                        } else if (obj instanceof ArrayReference) {
3656                                ArrayReference arr = (ArrayReference) obj;
3657                                if (looksLikePrimitiveArray (arr)) processPrimitiveArray (arr, out);
3658                                else processObjectArray (arr, out, visited);
3659                        } else {
3660                                List<Field> fs = ((ReferenceType) type).fields ();
3661                                if (isNode (type)) labelNodeWithSomePrimitiveFields (obj, fs, out);
3662                                else if (objectHasPrimitives (fs, obj)) labelObjectWithSomePrimitiveFields (obj, fs, out);
3663                                else labelObjectWithNoPrimitiveFields (obj, out);
3664                                if (!Format.matchesExcludePrefixShow (typeName)) {
3665                                        //System.err.println (typeName);
3666                                        String source = objectGvName (obj);
3667                                        for (Field f : fs) {
3668                                                Value value = obj.getValue (f);
3669                                                if (canIgnoreObjectField (f)) {
3670                                                        continue;
3671                                                }
3672                                                if (isNode (f)) {
3673                                                        processNodeStandalone (isNode (type), objectHasNonNodeReferences (fs, obj), source, f.name (), value, out, visited);
3674                                                } else if (!canTreatAsPrimitive (value)) {
3675                                                        processValueStandalone (source, Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES, f.name (), value, out, visited);
3676                                                }
3677                                        }
3678                                }
3679                        }
3680                }
3681        }
3682
3683        // ----------------------------------- static classes -----------------------------------------------
3684
3685        private static String staticClassName (ReferenceType type) {
3686                return Format.shortenFullyQualifiedName (type.name ());
3687        }
3688        private static String staticClassGvName (ReferenceType type) {
3689                return PREFIX_STATIC + type.classObject ().uniqueID ();
3690        }
3691        private static boolean staticClassHasFields (List<Field> fs) {
3692                for (Field f : fs) {
3693                        if (!canIgnoreStaticField (f)) return true;
3694                }
3695                return false;
3696        }
3697        private static boolean staticClassHasPrimitives (List<Field> fs, ReferenceType staticClass) {
3698                for (Field f : fs) {
3699                        if (canIgnoreStaticField (f)) continue;
3700                        if (canTreatAsPrimitive (staticClass.getValue (f))) return true;
3701                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3702                }
3703                return false;
3704        }
3705        private static void labelStaticClassWithNoPrimitiveFields (ReferenceType type, PrintWriter out) {
3706                String cabs = staticClassAttributeMap.get (type.name ());
3707                out.println (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3708        }
3709        private static void labelStaticClassWithSomePrimitiveFields (ReferenceType type, List<Field> fs, PrintWriter out) {
3710                out.print (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "|{");
3711                String sep = "";
3712                for (Field field : fs) {
3713                        if (!canIgnoreStaticField (field)) {
3714                                String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? field.name () + " = " : "";
3715                                if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, type.getValue (field), out)) sep = "|";
3716                        }
3717                }
3718                String cabs = staticClassAttributeMap.get (type.name ());
3719                out.println ("}\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3720        }
3721        private static void processStaticClass (ReferenceType type, PrintWriter out, Set<ObjectReference> visited) {
3722                String typeName = type.name ();
3723                List<Field> fs = type.fields ();
3724                if (!staticClassHasFields (fs)) {
3725                        return;
3726                }
3727                if (staticClassHasPrimitives (fs, type)) {
3728                        labelStaticClassWithSomePrimitiveFields (type, fs, out);
3729                } else {
3730                        labelStaticClassWithNoPrimitiveFields (type, out);
3731                }
3732                if (!Format.matchesExcludePrefixShow (type.name ())) {
3733                        String source = staticClassGvName (type);
3734                        for (Field f : fs) {
3735                                if (f.isStatic ()) {
3736                                        Value value = type.getValue (f);
3737                                        if ((!canIgnoreStaticField (f)) && (!canTreatAsPrimitive (value))) {
3738                                                String name = f.name ();
3739                                                processValueStandalone (source, Trace.GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES, name, value, out, visited);
3740                                        }
3741                                }
3742                        }
3743                }
3744        }
3745
3746        // ----------------------------------- frames -----------------------------------------------
3747        private static String frameName (int frameNumber, StackFrame frame, Method method, int lineNumber) {
3748                String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + frameNumber + " : " : "";
3749                return objString + Format.methodToString (method, true, false, ".") + " # " + lineNumber;
3750        }
3751        private static String frameGvName (int frameNumber) {
3752                return PREFIX_FRAME + frameNumber;
3753        }
3754        private static boolean frameHasPrimitives (Map<LocalVariable, Value> ls) {
3755                for (LocalVariable lv : ls.keySet ()) {
3756                        Value v = ls.get (lv);
3757                        if (canTreatAsPrimitive (v)) return true;
3758                        if (Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) return true;
3759                }
3760                return false;
3761        }
3762        private static void labelFrameWithNoPrimitiveLocals (int frameNumber, StackFrame frame, PrintWriter out) {
3763                Location location = frame.location ();
3764                ReferenceType type = location.declaringType ();
3765                Method method = location.method ();
3766                String attributes = frameAttributeMap.get (type.name ());
3767                out.println (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES
3768                                + (attributes == null ? "" : "," + attributes) + "];");
3769        }
3770        private static void labelFrameWithSomePrimitiveLocals (int frameNumber, StackFrame frame, Map<LocalVariable, Value> ls, PrintWriter out) {
3771                Location location = frame.location ();
3772                ReferenceType type = location.declaringType ();
3773                Method method = location.method ();
3774                out.print (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "|{");
3775                String sep = "";
3776                ObjectReference thisObject = frame.thisObject ();
3777                if (thisObject != null) {
3778                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? "this = " : "";
3779                        if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_VARIABLES, sep + name, thisObject, out)) sep = "|";
3780                }
3781                for (LocalVariable lv : ls.keySet ()) {
3782                        String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? lv.name () + " = " : "";
3783                        if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_VARIABLES, sep + name, ls.get (lv), out)) sep = "|";
3784                }
3785                String cabs = frameAttributeMap.get (type.name ());
3786                out.println ("}\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];");
3787        }
3788        private static boolean processFrame (int frameNumber, StackFrame frame, PrintWriter out, Set<ObjectReference> visited) {
3789                Location location = frame.location ();
3790                ReferenceType type = location.declaringType ();
3791                Method method = location.method ();
3792                if (Format.matchesExcludePrefixShow (type.name ())) return false;
3793
3794                Map<LocalVariable, Value> ls;
3795                try {
3796                        ls = frame.getValues (frame.visibleVariables ());
3797                } catch (AbsentInformationException e) {
3798                        return false;
3799                }
3800                if (frameHasPrimitives (ls)) {
3801                        labelFrameWithSomePrimitiveLocals (frameNumber, frame, ls, out);
3802                } else {
3803                        labelFrameWithNoPrimitiveLocals (frameNumber, frame, out);
3804                }
3805                ObjectReference thisObject = frame.thisObject ();
3806                if (!canTreatAsPrimitive (thisObject)) {
3807                        processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "this", thisObject, out, visited);
3808                }
3809                for (LocalVariable lv : ls.keySet ()) {
3810                        Value value = ls.get (lv);
3811                        if (!canTreatAsPrimitive (value)) {
3812                                processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, lv.name (), value, out, visited);
3813                        }
3814                }
3815                return true;
3816        }
3817
3818        // ----------------------------------- top level -----------------------------------------------
3819
3820        public static void drawFramesCheck (String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses) {
3821                if (Trace.drawStepsOfInternal (frames, returnVal)) {
3822                        int len = 0;
3823                        if (frames!=null) len = (Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME)? 2 : frames.size();
3824                        drawFrames (0, len, loc, returnVal, exnVal, frames, staticClasses, false);
3825                }
3826        }
3827        public static void drawFrames (int start, int len, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, boolean overrideShowStatics) {
3828                drawStuff (loc, (out) -> {
3829                        Set<ObjectReference> visited = new HashSet<> ();
3830                        if ((overrideShowStatics || Trace.GRAPHVIZ_SHOW_STATIC_CLASSES) && staticClasses != null) {
3831                                for (ReferenceType staticClass : staticClasses) {
3832                                        processStaticClass (staticClass, out, visited);
3833                                }
3834                        }
3835                        if (frames != null) {
3836                                for (int i = len - 1, prev = i; i >= start; i--) {
3837                                        StackFrame currentFrame = frames.get (i);
3838                                        Method meth = currentFrame.location ().method ();
3839                                        if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) continue;
3840                                        if (processFrame (len - i, currentFrame, out, visited)) {
3841                                                if (prev != i) {
3842                                                        out.println (frameGvName (len - i) + " -> " + frameGvName (len - prev) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3843                                                        prev = i;
3844                                                }
3845                                        }
3846                                }
3847                                // show the return value -- without this, it mysteriously disappears when drawing all steps
3848                                if (!Trace.GRAPHVIZ_SHOW_ONLY_TOP_FRAME && returnVal != null && !(returnVal instanceof VoidValue)) {
3849                                        String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + (len + 1) + " : " : "";
3850                                        //TODO
3851                                        if (canTreatAsPrimitive (returnVal) || Trace.GRAPHVIZ_SHOW_OBJECT_IDS_REDUNDANTLY) {
3852                                                out.print (PREFIX_RETURN + " [label=\"" + objString + "returnValue = ");
3853                                                processValueInline (true, "", returnVal, out);
3854                                                out.println ("\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3855                                        } else {
3856                                                out.println (PREFIX_RETURN + " [label=\"" + objString + "returnValue\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];");
3857                                        }
3858                                        if (!canTreatAsPrimitive (returnVal)) {
3859                                                processValueStandalone (PREFIX_RETURN, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", returnVal, out, visited);
3860                                        }
3861                                        out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3862                                }
3863                        }
3864                        // show the exception value
3865                        if (exnVal != null && !(exnVal instanceof VoidValue)) {
3866                                if (canTreatAsPrimitive (exnVal)) {
3867                                        out.print (PREFIX_EXCEPTION + " [label=\"exception = ");
3868                                        processValueInline (true, "", exnVal, out);
3869                                        out.println ("\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3870                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3871                                } else {
3872                                        out.println (PREFIX_EXCEPTION + " [label=\"exception\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];");
3873                                        processValueStandalone (PREFIX_EXCEPTION, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", exnVal, out, visited);
3874                                        if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];");
3875                                }
3876                        }
3877                });
3878        }
3879        public static void drawObjects (String loc, Map<String, ObjectReference> objects) {
3880                drawStuff (loc, (out) -> {
3881                        Set<ObjectReference> visited = new HashSet<> ();
3882                        for (String key : objects.keySet ()) {
3883                                processObjectWithLabel (key, objects.get (key), out, visited);
3884                        }
3885                });
3886        }
3887        protected static void drawStuff (String loc, Consumer<PrintWriter> consumer) {
3888                String filenamePrefix = nextFilename ();
3889                String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3890                File gvFile = new File (filenamePrefix + theLoc + ".gv");
3891                PrintWriter out;
3892                try {
3893                        out = new PrintWriter (new FileWriter (gvFile));
3894                } catch (IOException e) {
3895                        throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3896                }
3897                out.println ("digraph Java {");
3898                consumer.accept (out);
3899                out.println ("}");
3900                out.close ();
3901                //System.err.println (gvFile);
3902                if (!Trace.GRAPHVIZ_RUN_GRAPHVIZ) {
3903                        System.err.println ("\n!!!! Not running graphviz because Trace.GRAPHVIZ_RUN_GRAPHVIZ is false.");                       
3904                } else {
3905                        String executable = null;
3906                        for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3907                                if (new File (s).canExecute ()) executable = s;
3908                        }
3909                        if (executable == null) {
3910                                System.err.println ("\n!!!! Make sure Graphviz is installed.  Go to https://www.graphviz.org/download/");                                                       
3911                                System.err.println ("\n!!!! Graphviz executable not found in: " + Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS);                                                       
3912                        } else {
3913                                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_OUTPUT_FORMAT);
3914                                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_OUTPUT_FORMAT);
3915                                pb.redirectInput (gvFile);
3916                                pb.redirectOutput (outFile);
3917                                int result = -1;
3918                                try {
3919                                        result = pb.start ().waitFor ();
3920                                } catch (IOException e) {
3921                                        throw new Error ("\n!!!! Cannot execute " + executable + "\n!!!! Make sure you have installed http://www.graphviz.org/"
3922                                                        + "\n!!!! Check the value of GRAPHVIZ_POSSIBLE_DOT_LOCATIONS in " + Trace.class.getCanonicalName ());
3923                                } catch (InterruptedException e) {
3924                                        throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3925                                }
3926                                if (result == 0) {
3927                                        if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3928                                                gvFile.delete ();
3929                                        }
3930                                } else {
3931                                        outFile.delete ();
3932                                }
3933                        }
3934                }
3935        }
3936
3937        //    public static void drawFrames (int start, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, Map<String, ObjectReference> objects) {
3938        //        String filenamePrefix = nextFilename ();
3939        //        String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : "";
3940        //        File gvFile = new File (filenamePrefix + theLoc + ".gv");
3941        //        PrintWriter out;
3942        //        try {
3943        //            out = new PrintWriter (new FileWriter (gvFile));
3944        //        } catch (IOException e) {
3945        //            throw new Error ("\n!!!! Cannot open " + gvFile + "for writing");
3946        //        }
3947        //        processFrames (start, returnVal, exnVal, frames, staticClasses, objects, out);
3948        //        out.close ();
3949        //        //System.err.println (gvFile);
3950        //        if (Trace.GRAPHVIZ_RUN_DOT) {
3951        //            String executable = null;
3952        //            for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) {
3953        //                if (new File (s).canExecute ())
3954        //                    executable = s;
3955        //            }
3956        //            if (executable != null) {
3957        //                ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3958        //                File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT);
3959        //                pb.redirectInput (gvFile);
3960        //                pb.redirectOutput(outFile);
3961        //                int result = -1;
3962        //                try {
3963        //                    result = pb.start ().waitFor ();
3964        //                } catch (IOException e) {
3965        //                    throw new Error ("\n!!!! Cannot execute " + executable +
3966        //                            "\n!!!! Make sure you have installed http://www.graphviz.org/" +
3967        //                            "\n!!!! Check the value of GRAPHVIZ_DOT_COMMAND in " + Trace.class.getCanonicalName ());
3968        //                } catch (InterruptedException e) {
3969        //                    throw new Error ("\n!!!! Execution of " + executable + "interrupted");
3970        //                }
3971        //                if (result == 0) {
3972        //                    if (Trace.GRAPHVIZ_REMOVE_GV_FILES) {
3973        //                        gvFile.delete ();
3974        //                    }
3975        //                } else {
3976        //                    outFile.delete ();
3977        //                }
3978        //            }
3979        //        }
3980        //    }
3981}