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