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