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