001    package daikon.chicory;
002    
003    import java.util.regex.Pattern;
004    import java.util.zip.GZIPOutputStream;
005    import java.io.*;
006    import java.net.*;
007    import java.net.Socket;
008    import java.util.*;
009    
010    /**
011     * Runtime support for Chicory, the Daikon front end for Java.
012     * This class is a collection of methods; it should never be instantiated.
013     */
014    public class Runtime
015    {
016        /** Unique id for method entry/exit (so they can be matched up) **/
017        public static int nonce = 0;
018    
019        /** debug flag **/
020        public static final boolean debug = false;
021    
022        /**
023         * Flag indicating that a dtrace record is currently being written
024         * used to prevent a call to instrumented code that occurs as part
025         * of generating a dtrace record (eg, toArray when processing lists
026         * or pure functions) from generating a nested dtrace record
027         */
028        public static boolean in_dtrace = false;
029    
030        /** True if ChicoryPremain was unable to load. **/
031        public static boolean chicoryLoaderInstantiationError = false;
032    
033        /**
034         * List of classes recently transformed.  This list is examined in
035         * each enter/exit and the decl information for any new classes are
036         * printed out and the class is then removed from the list.
037         */
038        public static final List<ClassInfo> new_classes
039          = new LinkedList<ClassInfo>();
040    
041        /** List of all instrumented classes **/
042        public static final List<ClassInfo> all_classes
043          = new ArrayList<ClassInfo>();
044    
045        /** flag that indicates when the first class has been processed**/
046        static boolean first_class = true;
047    
048        /** List of all instrumented methods **/
049        public static final List<MethodInfo> methods = new ArrayList<MethodInfo>();
050    
051        //
052        // Control over what classes (ppts) are instrumented
053        //
054        /** Ppts to omit (regular expression) **/
055        public static List<Pattern> ppt_omit_pattern = new ArrayList<Pattern>();
056    
057        /** Ppts to include (regular expression) **/
058        public static List<Pattern> ppt_select_pattern = new ArrayList<Pattern>();
059    
060        /** Comparability information (if any) **/
061        static /*@Nullable*/ DeclReader comp_info = null;
062    
063        //
064        // Setups that control what information is written
065        //
066        /** Render linked lists as vectors **/
067        static boolean linked_lists = true;
068    
069        /** Depth to wich to examine structure components **/
070        static int nesting_depth = 2;
071    
072        //
073        // Dtrace file vars
074        //
075        /** Max number of records in dtrace file **/
076        static long dtraceLimit = Long.MAX_VALUE;
077    
078        /** Number of records printed to date **/
079        static long printedRecords = 0;
080    
081        /** Terminate the program when the dtrace limit is reached **/
082        static boolean dtraceLimitTerminate = false;
083    
084        /** Dtrace output stream **/
085        static PrintStream dtrace;
086    
087        /** Set to true when the dtrace stream is closed **/
088        static boolean dtrace_closed = false;
089    
090        /** True if no dtrace is being generated.  **/
091        static boolean no_dtrace = false;
092    
093        /** Decl writer setup for writing to the trace file **/
094        // Set in ChicoryPremain.premain().
095        static DeclWriter decl_writer;
096    
097        /** Dtrace writer setup for writing to the trace file **/
098        // Set in ChicoryPremain.premain().
099        static DTraceWriter dtrace_writer;
100    
101        /**
102         * Which static initializers have been run.
103         * Each element of the Set is a fully qualified class name.
104         **/
105        private static Set<String> initSet = new HashSet<String>();
106    
107        /** Class of information about each active call **/
108        private static class CallInfo {
109            /** nonce of call **/
110            int nonce;
111            /** whether or not the call was captured on enter **/
112            boolean captured;
113            public CallInfo (int nonce, boolean captured) {
114                this.nonce = nonce; this.captured = captured;
115            }
116        }
117    
118        /** Stack of active methods. **/
119        private static Map<Thread,Stack<CallInfo>> thread_to_callstack
120            = new LinkedHashMap<Thread,Stack<CallInfo>>();
121    
122      /**
123       * Sample count at a call site to begin sampling.  All previous calls
124       * will be recorded.  Sampling starts at 10% and decreases by a factor
125       * of 10 each time another sample_start samples have been recorded.  If
126       * sample_start is 0, then all calls will be recorded.
127       */
128      public static int sample_start = 0;
129    
130        // Constructor
131        private Runtime()
132        {
133            throw new Error("Do not create instances of Runtime");
134        }
135    
136        /** Printf to dtrace file. **/
137        final private static void printf(String format, /*@Nullable*/ Object... args)
138        {
139            if (!dtrace_closed)
140                dtrace.printf(format, args);
141        }
142    
143        /** Println to dtrace file. **/
144        final private static void println(String msg)
145        {
146            if (!dtrace_closed)
147                dtrace.println(msg);
148        }
149    
150        /** Println to dtrace file. **/
151        final private static void println(int val)
152        {
153            if (!dtrace_closed)
154                dtrace.println(val);
155        }
156    
157        /** Println to dtrace file. **/
158        final private static void println(Object obj)
159        {
160            if (!dtrace_closed)
161                dtrace.println(obj);
162        }
163    
164        /** Println to dtrace file. **/
165        final private static void println()
166        {
167            if (!dtrace_closed)
168                dtrace.println();
169        }
170    
171        /**
172         * Thrown to indicate that main should not print a stack trace, but only
173         * print the message itself to the user.
174         * If the string is null, then this is normal termination, not an error.
175         **/
176        public static class TerminationMessage extends RuntimeException
177        {
178            static final long serialVersionUID = 20050923L;
179    
180            public TerminationMessage(String s)
181            {
182                super(s);
183            }
184    
185            public TerminationMessage()
186            {
187                super();
188            }
189        }
190    
191    
192        private static boolean invokingPure = false;
193        public static boolean dontProcessPpts()
194        {
195            return invokingPure;
196        }
197        public static void startPure()
198        {
199            invokingPure = true;
200        }
201        public static void endPure()
202        {
203            invokingPure = false;
204        }
205    
206        /**
207         * Called when a method is entered.
208         *
209         * @param obj - Receiver of the method that was entered.  Null if method is
210         *              static
211         * @param nonce - Nonce identifying which enter/exit pair this is
212         * @param mi_index - Index in methods of the MethodInfo for this method
213         * @param args - Array of arguments to method
214         */
215        public static synchronized void enter(/*@Nullable*/ Object obj, int nonce, int mi_index,
216                                              Object[] args) {
217    
218          if (dontProcessPpts())
219            return;
220    
221          // Make sure that the in_dtrace flag matches the stack trace
222          // check_in_dtrace();
223    
224          // Ignore this call if we are already processing a dtrace record
225          if (in_dtrace)
226            return;
227    
228          // Note that we are processing a dtrace record until we return
229          in_dtrace = true;
230          try {
231            int num_new_classes = 0;
232            synchronized (new_classes) {
233              num_new_classes = new_classes.size();
234            }
235            if (num_new_classes > 0)
236              process_new_classes();
237    
238            MethodInfo mi = methods.get(mi_index);
239            mi.call_cnt++;
240    
241            // If sampling, check to see if we are capturing this sample
242            boolean capture = true;
243            if (sample_start > 0) {
244              if (mi.call_cnt <= sample_start)
245                ;
246              else if (mi.call_cnt <= (sample_start*10))
247                capture = (mi.call_cnt % 10) == 0;
248              else if (mi.call_cnt <= (sample_start*100))
249                capture = (mi.call_cnt % 100) == 0;
250              else if (mi.call_cnt <= (sample_start*1000))
251                capture = (mi.call_cnt % 1000) == 0;
252              else
253                capture = (mi.call_cnt % 10000) == 0;
254              Thread t = Thread.currentThread();
255              Stack<CallInfo> callstack = thread_to_callstack.get (t);
256              if (callstack == null) {
257                  callstack = new Stack<CallInfo>();
258                  thread_to_callstack.put (t, callstack);
259              }
260              callstack.push (new CallInfo (nonce, capture));
261            }
262    
263            if (capture) {
264              mi.capture_cnt++;
265              // long start = System.currentTimeMillis();
266              dtrace_writer.methodEntry(mi, nonce, obj, args);
267              // long duration = System.currentTimeMillis() - start;
268              //System.out.println ("Enter " + mi + " " + duration + "ms"
269              //                 + " " + mi.capture_cnt + "/" + mi.call_cnt);
270            } else {
271              //System.out.println ("skipped " + mi
272              //                 + " " + mi.capture_cnt + "/" + mi.call_cnt);
273            }
274          } finally {
275            in_dtrace = false;
276          }
277        }
278    
279        /**
280         * Called when a method is exited.
281         *
282         * @param obj        -  Receiver of the method that was entered.  Null if method is
283         *                      static
284         * @param nonce       - Nonce identifying which enter/exit pair this is
285         * @param mi_index    - Index in methods of the MethodInfo for this method
286         * @param args        - Array of arguments to method
287         * @param ret_val     - Return value of method.  null if method is void
288         * @param exitLineNum - The line number at which this method exited
289         */
290        public static synchronized void exit(/*@Nullable*/ Object obj, int nonce, int mi_index,
291                                Object[] args, Object ret_val, int exitLineNum) {
292          if (dontProcessPpts())
293            return;
294    
295          // Make sure that the in_dtrace flag matches the stack trace
296          // check_in_dtrace();
297    
298          // Ignore this call if we are already processing a dtrace record
299          if (in_dtrace)
300            return;
301    
302          // Note that we are processing a dtrace record until we return
303          in_dtrace = true;
304          try {
305    
306            int num_new_classes = 0;
307            synchronized (new_classes) {
308              num_new_classes = new_classes.size();
309            }
310            if (num_new_classes > 0)
311              process_new_classes();
312    
313            // Skip this call if it was not sampled at entry to the method
314            if (sample_start > 0) {
315              CallInfo ci = null;
316              @SuppressWarnings("nullness") // Map.get: key was put in map by enter()
317              /*@NonNull*/ Stack<CallInfo> callstack
318                  = thread_to_callstack.get (Thread.currentThread());
319              while (!callstack.empty()) {
320                ci = callstack.pop();
321                if (ci.nonce == nonce)
322                  break;
323              }
324              if (ci == null) {
325                System.out.printf ("no enter for exit %s%n", methods.get(mi_index));
326                return;
327              } else if (!ci.captured) {
328                return;
329              }
330            }
331    
332            // Write out the infromation for this method
333            MethodInfo mi = methods.get(mi_index);
334            // long start = System.currentTimeMillis();
335            dtrace_writer.methodExit(mi, nonce, obj, args, ret_val,
336                                     exitLineNum);
337            // long duration = System.currentTimeMillis() - start;
338            // System.out.println ("Exit " + mi + " " + duration + "ms");
339          } finally {
340            in_dtrace = false;
341          }
342        }
343    
344        /**
345         * Checks the in_dtrace flag by looking back up the stack trace.
346         * Throws an exception if there is a discrepancy.
347         */
348        private static void check_in_dtrace() {
349    
350          Throwable st = new Throwable();
351          st.fillInStackTrace();
352          List<StackTraceElement> enter_exit_list
353            = new ArrayList<StackTraceElement>();
354          for (StackTraceElement ste : st.getStackTrace()) {
355            if (ste.getClassName().endsWith ("chicory.Runtime")
356                && (ste.getMethodName().equals ("enter")
357                    || ste.getMethodName().equals ("exit")))
358              enter_exit_list.add (ste);
359          }
360          if (in_dtrace && (enter_exit_list.size() <= 1)) {
361            throw new RuntimeException ("in dtrace and stack contains "
362                                        + enter_exit_list);
363          } else if (!in_dtrace && (enter_exit_list.size() > 1)) {
364            throw new RuntimeException ("not in dtrace and stack contains "
365                                        + enter_exit_list);
366          }
367        }
368    
369        /**
370         * Called by classes when they have finished initialization
371         * (i.e., their static initializer has completed).
372         *
373         * This functionality must be enabled by the flag
374         * Chicory.checkStaticInit.  When enabled, this method should only
375         * be called by the hooks created in the Instrument class.
376         *
377         * @param className Fully qualified class name
378         */
379        public static void initNotify(String className)
380        {
381            assert !initSet.contains(className) : className + " already exists in initSet";
382    
383            //System.out.println("initialized ---> " + name);
384            initSet.add(className);
385        }
386    
387        /**
388         * Return true iff the class with fully qualified name className
389         * has been initialized.
390         *
391         * @param className Fully qualified class name
392         */
393        public static boolean isInitialized(String className)
394        {
395            return initSet.contains(className);
396        }
397    
398        /**
399         * Writes out decl information for any new classes and removes
400         * them from the list.
401         */
402        public static void process_new_classes() {
403    
404          // Processing of the new_classes list must be
405          // very careful, as the call to get_reflection or printDeclClass
406          // may load other classes (which then get added to the list).
407          while (true) {
408    
409            // Get the first class in the list (if any)
410            ClassInfo class_info = null;
411            synchronized (new_classes) {
412              if (new_classes.size() > 0) {
413                class_info = new_classes.get (0);
414                new_classes.remove (0);
415              }
416            }
417            if (class_info == null)
418              break;
419    
420            if (debug)
421              System.out.println ("processing class " + class_info.class_name);
422            if (first_class) {
423              decl_writer.printHeaderInfo (class_info.class_name);
424              first_class = false;
425            }
426            class_info.initViaReflection();
427            // class_info.dump (System.out);
428    
429            // Create tree structure for all method entries/exits in the class
430            for (MethodInfo mi: class_info.method_infos)
431            {
432                mi.traversalEnter = RootInfo.enter_process(mi, Runtime.nesting_depth);
433                mi.traversalExit = RootInfo.exit_process(mi, Runtime.nesting_depth);
434            }
435    
436            decl_writer.printDeclClass (class_info, comp_info);
437    
438          }
439        }
440    
441        /** Increment the number of records that have been printed. **/
442        public static void incrementRecords()
443        {
444            printedRecords++;
445    
446            // This should only print a percentage if dtraceLimit is not its
447            // default value.
448            // if (printedRecords%1000 == 0)
449            //     System.out.printf("printed=%d, percent printed=%f%n", printedRecords, (float)(100.0*(float)printedRecords/(float)dtraceLimit));
450    
451            if (printedRecords >= dtraceLimit)
452            {
453                noMoreOutput();
454            }
455        }
456    
457        /** Indicates that no more output should be printed to the dtrace file.
458         *  The file is closed and iff dtraceLimitTerminate is true the program
459         * is terminated.
460         */
461        public static void noMoreOutput()
462        {
463            // The incrementRecords method (which calls this) is called inside a
464            // synchronized block, but re-synchronize just to be sure, or in case
465            // this is called from elsewhere.
466            synchronized (Runtime.dtrace)
467            {
468                // The shutdown hook is synchronized on this, so close it up
469                // ourselves, lest the call to System.exit cause deadlock.
470                dtrace.println();
471                dtrace.println("# EOF (added by no_more_output)");
472                dtrace.close();
473    
474                // Don't set dtrace to null, because if we continue running, there will
475                // be many attempts to synchronize on it.  (Is that a performance
476                // bottleneck, if we continue running?)
477                // dtrace = null;
478                dtrace_closed = true;
479    
480    
481                if (dtraceLimitTerminate)
482                {
483                    System.out.println("Printed " + printedRecords + " records to dtrace file.  Exiting.");
484                    throw new TerminationMessage("Printed " + printedRecords + " records to dtrace file.  Exiting.");
485                    // System.exit(1);
486                }
487                else
488                {
489                    // By default, no special output if the system continues to run.
490                    no_dtrace = true;
491                }
492            }
493        }
494    
495        public static void setDtraceOnlineMode(int port)
496        {
497            dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
498            dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
499    
500            Socket daikonSocket = null;
501            try
502            {
503                daikonSocket = new Socket();
504                @SuppressWarnings("nullness") // unannotated: java.net.Socket is not yet annotated
505                /*@NonNull*/ SocketAddress dummy = null;
506                daikonSocket.bind(dummy);
507                //System.out.println("Attempting to connect to Daikon on port --- " + port);
508                daikonSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(), port), 5000);
509            }
510            catch (UnknownHostException e)
511            {
512                System.out.println("UnknownHostException connecting to Daikon : " + e.getMessage() + ". Exiting");
513                System.exit(1);
514            }
515            catch (IOException e)
516            {
517                System.out.println("IOException, could not connect to Daikon : " + e.getMessage() + ". Exiting");
518                System.exit(1);
519            }
520    
521            try
522            {
523                dtrace = new PrintStream(daikonSocket.getOutputStream());
524            }
525            catch (IOException e)
526            {
527                System.out.println("IOException connecting to Daikon : " + e.getMessage() + ". Exiting");
528                System.exit(1);
529            }
530    
531            if (supportsAddShutdownHook())
532            {
533                addShutdownHook();
534            }
535            else
536            {
537                System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
538            }
539        }
540    
541        // Copied from daikon.Runtime
542        /** Specify the dtrace file to which to write **/
543        public static void setDtrace(String filename, boolean append)
544        {
545            System.out.printf("entered daikon.chicory.Runtime.setDtrace(%s, %b)...%n", filename, append);
546    
547            if (no_dtrace)
548            {
549                throw new Error("setDtrace called when no_dtrace was specified");
550            }
551            try
552            {
553                File file = new File(filename);
554                File parent = file.getParentFile();
555                if (parent != null)
556                    parent.mkdirs();
557                OutputStream os = new FileOutputStream(filename, append);
558                if (filename.endsWith(".gz"))
559                {
560                    if (append)
561                        throw new Error("DTRACEAPPEND environment variable is set, " + "Cannot append to gzipped dtrace file " + filename);
562                    os = new GZIPOutputStream(os);
563                }
564                dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
565                dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
566    
567                //System.out.println("limit = " + dtraceLimit + " terminate " + dtraceLimitTerminate);
568    
569                // 8192 is the buffer size in BufferedReader
570                BufferedOutputStream bos = new BufferedOutputStream(os, 8192);
571                dtrace = new PrintStream(bos);
572            }
573            catch (Exception e)
574            {
575                e.printStackTrace();
576                throw new Error(e);
577            }
578            if (supportsAddShutdownHook())
579            {
580                addShutdownHook();
581            }
582            else
583            {
584                System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
585            }
586            // System.out.printf("exited daikon.chicory.Runtime.setDtrace(%s, %b)%n", filename, append);
587        }
588    
589        /**
590         * If the current data trace file is not yet set, then set it.
591         * The value of the DTRACEFILE environment variable is used;
592         * if that environment variable is not set, then the argument
593         * to this method is used instead.
594         **/
595        public static void setDtraceMaybe(String default_filename)
596        {
597            // Copied from daikon.Runtime
598            // System.out.println ("Setting dtrace maybe: " + default_filename);
599            if ((dtrace == null) && (!no_dtrace))
600            {
601                String filename = System.getProperty("DTRACEFILE", default_filename);
602                boolean append = System.getProperty("DTRACEAPPEND") != null;
603                setDtrace(filename, append);
604            }
605        }
606    
607        private static boolean supportsAddShutdownHook()
608        {
609            // Copied from daikon.Runtime
610    
611            try
612            {
613                Class<java.lang.Runtime> rt = java.lang.Runtime.class;
614                rt.getMethod("addShutdownHook", new Class<?>[]{java.lang.Thread.class});
615                return true;
616            }
617            catch (Exception e)
618            {
619                return false;
620            }
621        }
622    
623        /**
624         * Add a shutdown hook to close the PrintStream when the program
625         * exits.
626         */
627        private static void addShutdownHook()
628        {
629            // Copied from daikon.Runtime, then modified
630    
631            java.lang.Runtime.getRuntime().addShutdownHook(new Thread()
632            {
633                public void run()
634                {
635                    if (!dtrace_closed)
636                    {
637                        // When the program being instrumented exits, the buffers
638                        // of the "dtrace" (PrintStream) object are not flushed,
639                        // so we miss the tail of the file.
640    
641                        synchronized (Runtime.dtrace)
642                        {
643                            dtrace.println();
644                            // These are for debugging, I assume. -MDE
645                            for (Pattern p : ppt_omit_pattern)
646                                dtrace.println ("# ppt-omit-pattern: " + p);
647                            for (Pattern p : ppt_select_pattern)
648                                dtrace.println ("# ppt-select-pattern: " + p);
649                            // This lets us know we didn't lose any data.
650                            dtrace.println("# EOF (added by Runtime.addShutdownHook)");
651                            dtrace.close();
652                        }
653                    }
654    
655                    if (chicoryLoaderInstantiationError) {
656                        // Warning messages have already been printed.
657                    } else if (all_classes.size() == 0) {
658                        System.out.println ("Chicory warning: No methods were instrumented.");
659                        if ((! ppt_select_pattern.isEmpty()) || (! ppt_omit_pattern.isEmpty())) {
660                            System.out.println ("Check the --ppt-select-pattern and --ppt-omit-pattern options");
661                        }
662                    } else if (printedRecords == 0) {
663                        System.out.println ("Chicory warning: no records were printed");
664                    }
665                }
666            });
667        }
668    
669        private static Process chicory_proc;
670    
671        private static StreamRedirectThread err_thread;
672    
673        private static StreamRedirectThread out_thread;
674    
675    
676        static void setDaikonInfo(StreamRedirectThread err, StreamRedirectThread out, Process proc)
677        {
678            chicory_proc = proc;
679            err_thread = err;
680            out_thread = out;
681        }
682    
683        /**
684         * Wait for Daikon to terminate
685         */
686        public static void endDaikon()
687        {
688            try
689            {
690                int status = chicory_proc.waitFor();
691                System.out.println("daikon ended with status " + status);
692            }
693            catch (InterruptedException e1)
694            {
695                e1.printStackTrace();
696            }
697    
698            try
699            {
700                err_thread.join();
701                out_thread.join();
702            }
703            catch(InterruptedException e)
704            {
705            }
706    
707            System.out.println("Finished endDaikon");
708    
709        }
710    
711        /**
712         * Gets the ClassInfo structure corresponding to type.  Returns null
713         * if the class was not instrumented.
714         * @param type declaring class
715         * @return ClassInfo structure corresponding to type
716         */
717        public static /*@Nullable*/ ClassInfo getClassInfoFromClass(Class<?> type)
718        {
719            try {
720              synchronized (Runtime.all_classes) {
721                for (ClassInfo cinfo : Runtime.all_classes) {
722                  if (cinfo.clazz == null)
723                    cinfo.initViaReflection();
724    
725                  if (cinfo.clazz.equals(type))
726                    return cinfo;
727                }
728              }
729            }
730            catch(ConcurrentModificationException e) {
731              // occurs if cinfo.get_reflection() causes a new class to be loaded
732              // which causes all_classes to change
733              return getClassInfoFromClass(type);
734            }
735    
736            // throw new RuntimeException("Unable to find class " + type.getName() + " in Runtime's class list");
737            return null;
738        }
739    
740    
741      ///////////////////////////////////////////////////////////////////////////
742      /// Wrappers for the various primitive types.
743      /// Used to distinguish wrappers created by user code
744      /// from wrappers created by Chicory.
745    
746      public static interface PrimitiveWrapper
747      {
748      }
749    
750      /** wrapper used for boolean arguments **/
751      public static class BooleanWrap implements PrimitiveWrapper{
752        boolean val;
753        public BooleanWrap (boolean val) { this.val = val; }
754        public String toString() {return Boolean.toString(val);}
755      }
756    
757      /** wrapper used for int arguments **/
758      public static class ByteWrap implements PrimitiveWrapper{
759        byte val;
760        public ByteWrap (byte val) { this.val = val; }
761        public String toString() {return Byte.toString(val);}
762      }
763    
764      /** wrapper used for int arguments **/
765      public static class CharWrap implements PrimitiveWrapper{
766        char val;
767        public CharWrap (char val) { this.val = val; }
768        // Print characters as integers.
769        public String toString() {return Integer.toString(val);}
770      }
771    
772      /** wrapper used for int arguments **/
773      public static class FloatWrap implements PrimitiveWrapper{
774        float val;
775        public FloatWrap (float val) { this.val = val; }
776        public String toString() {return Float.toString(val);}
777      }
778    
779      /** wrapper used for int arguments **/
780      public static class IntWrap implements PrimitiveWrapper{
781        int val;
782        public IntWrap (int val) { this.val = val; }
783        public String toString() {return Integer.toString(val);}
784      }
785    
786      /** wrapper used for int arguments **/
787      public static class LongWrap implements PrimitiveWrapper{
788        long val;
789        public LongWrap (long val) { this.val = val; }
790        public String toString() {return Long.toString(val);}
791      }
792    
793      /** wrapper used for int arguments **/
794      public static class ShortWrap implements PrimitiveWrapper{
795        short val;
796        public ShortWrap (short val) { this.val = val; }
797        public String toString() {return Short.toString(val);}
798      }
799    
800      /** wrapper used for double arguments **/
801      public static class DoubleWrap implements PrimitiveWrapper{
802        double val;
803        public DoubleWrap (double val) { this.val = val; }
804        public String toString() {return Double.toString(val);}
805      }
806    
807    
808      ///////////////////////////////////////////////////////////////////////////
809      /// Copied code
810      ///
811    
812      // Lifted directly from plume/UtilMDE.java, where it is called
813      // escapeNonJava(), but repeated here to make this class self-contained.
814      /** Quote \, ", \n, and \r characters in the target; return a new string. **/
815      public static String quote(String orig) {
816        StringBuffer sb = new StringBuffer();
817        // The previous escape (or escaped) character was seen right before
818        // this position.  Alternately:  from this character forward, the string
819        // should be copied out verbatim (until the next escaped character).
820        int post_esc = 0;
821        int orig_len = orig.length();
822        for (int i=0; i<orig_len; i++) {
823          char c = orig.charAt(i);
824          switch (c) {
825          case '\"':
826          case '\\':
827            if (post_esc < i) {
828              sb.append(orig.substring(post_esc, i));
829            }
830            sb.append('\\');
831            post_esc = i;
832            break;
833          case '\n':                // not lineSep
834            if (post_esc < i) {
835              sb.append(orig.substring(post_esc, i));
836            }
837            sb.append("\\n");       // not lineSep
838            post_esc = i+1;
839            break;
840          case '\r':
841            if (post_esc < i) {
842              sb.append(orig.substring(post_esc, i));
843            }
844            sb.append("\\r");
845            post_esc = i+1;
846            break;
847          default:
848            // Do nothing; i gets incremented.
849          }
850        }
851        if (sb.length() == 0)
852          return orig;
853        sb.append(orig.substring(post_esc));
854        return sb.toString();
855      }
856    
857      private static HashMap<String, String> primitiveClassesFromJvm = new HashMap<String, String>(8);
858      static {
859        primitiveClassesFromJvm.put("Z", "boolean");
860        primitiveClassesFromJvm.put("B", "byte");
861        primitiveClassesFromJvm.put("C", "char");
862        primitiveClassesFromJvm.put("D", "double");
863        primitiveClassesFromJvm.put("F", "float");
864        primitiveClassesFromJvm.put("I", "int");
865        primitiveClassesFromJvm.put("J", "long");
866        primitiveClassesFromJvm.put("S", "short");
867      }
868    
869      /**
870       * Convert a classname from JVML format to Java format.
871       * For example, convert "[Ljava/lang/Object;" to "java.lang.Object[]".
872       **/
873      public static String classnameFromJvm(String classname) {
874    
875          //System.out.println(classname);
876    
877        int dims = 0;
878        while (classname.startsWith("[")) {
879          dims++;
880          classname = classname.substring(1);
881        }
882    
883        String result;
884        //array of reference type
885        if (classname.startsWith("L") && classname.endsWith(";")) {
886          result = classname.substring(1, classname.length() - 1);
887          result = result.replace('/', '.');
888        }
889        else {
890            if (dims > 0) //array of primitives
891                result = primitiveClassesFromJvm.get(classname);
892            else //just a primitive
893                result = classname;
894    
895          if (result == null) {
896            // As a failsafe, use the input; perhaps it is in Java, not JVML,
897            // format.
898            result = classname;
899            // throw new Error("Malformed base class: " + classname);
900          }
901        }
902        for (int i=0; i<dims; i++) {
903          result += "[]";
904        }
905        return result;
906      }
907    
908      ///////////////////////////////////////////////////////////////////////////
909      /// end of copied code
910      ///
911    
912    }