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