001    /*
002     * Copyright (C) 2000-2001 Iowa State University
003     *
004     * This file is part of mjc, the MultiJava Compiler, adapted for ESC/Java2.
005     *
006     * This program is free software; you can redistribute it and/or modify
007     * it under the terms of the GNU General Public License as published by
008     * the Free Software Foundation; either version 2 of the License, or
009     * (at your option) any later version.
010     *
011     * This program is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014     * GNU General Public License for more details.
015     *
016     * You should have received a copy of the GNU General Public License
017     * along with this program; if not, write to the Free Software
018     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019     *
020     * $Id: Utils.java,v 1.13 2006/08/13 02:36:41 chalin Exp $
021     * Author: David R. Cok
022     */
023    
024    package junitutils;
025    import java.io.PrintStream;
026    import java.io.*;
027    import java.lang.reflect.Method;
028    
029    /** This class contains miscellaneous (static) utility functions that are
030     useful in writing JUnit functional tests.
031     */
032    public class Utils {
033      
034      /** Setting this field to true disables the capturing of the output;
035       one would do this only for debugging purposes.
036       */
037      static public boolean disable = false;
038      
039      /** A cached value of the usual System out stream. */
040      //@ non_null spec_public
041      final private static PrintStream pso = System.out;
042    
043      /** A cached value of the usual System err stream. */
044      //@ non_null spec_public
045      final private static PrintStream pse = System.err;
046      
047      /** Redirects System.out and System.err to the given PrintStream. 
048       Note that setStreams/restoreStreams operate on the global
049       values of System.out and System.err; these implementations
050       are not synchronized - you will need to take care of any
051       race conditions if you utilize these in more than one thread.
052       
053       @param ps The stream that is the new output and error stream
054       */
055        /*@ public normal_behavior
056          @   requires disable;
057          @   assignable \nothing;
058          @ also
059          @ public normal_behavior
060          @   requires !disable;
061          @   requires ps.isOpen;
062          @   assignable System.out, System.err;
063          @   ensures System.out == ps 
064          @        && System.err == ps;
065          @*/
066      static public void setStreams(/*@ non_null */ PrintStream ps) {
067        if (disable) return;
068        System.out.flush();
069        System.err.flush();
070        System.setOut(ps);
071        System.setErr(ps);
072      }
073      
074      /** Creates a new output stream (which is returned) and makes it
075       the stream into which the standard and error outputs are captured.
076       
077       @return an output stream into which standard and error output 
078       is captured
079       */
080        /*@ public normal_behavior
081          @   requires disable;
082          @   assignable \nothing;
083          @   ensures \fresh(\result);
084          @ also
085          @ public normal_behavior
086          @   requires !disable;
087          @   assignable System.out, System.err;
088          @   ensures \fresh(\result);
089          @   ensures \result.isOpen;
090          @   ensures System.out.underlyingStream == \result;
091          @   ensures System.err.underlyingStream == \result;
092          @   ensures System.err == System.out;
093          @*/
094      static public /*@ non_null */ ByteArrayOutputStream setStreams() {
095        ByteArrayOutputStream ba = new ByteArrayOutputStream(10000);
096        PrintStream ps = new PrintStream(ba);
097        setStreams(ps);
098        return ba;
099      }
100      // TODO - note the hard-coded size of the stream above.  It needs to be
101      // variable, or at least be sure to capture overflows.  The size needs to
102      // be large enough to hold the output of a test.
103      
104      /** Restores System.out and System.err to the initial, 
105       system-defined values. It is ok to call this method
106       even if setStreams has not been called.
107       <p>
108       Note that setStreams/restoreStreams operate on the global
109       values of System.out and System.err; these implementations
110       are not synchronized - you will need to take care of any
111       race conditions if you utilize these in more than one thread.
112       */
113        /*@ public normal_behavior
114          @   requires disable;
115          @   assignable \nothing;
116          @ also
117          @ public normal_behavior
118          @   requires !disable;
119          @   assignable System.out, System.err;
120          @   ensures System.out == pso
121          @        && System.err == pse;
122          @*/
123      static public void restoreStreams() {
124        restoreStreams(false);
125      }
126      
127      /** Restores System.out and System.err to the initial, 
128       system-defined values. It is ok to call this method
129       even if setStreams has not been called.
130       <p>
131       Note that setStreams/restoreStreams operate on the global
132       values of System.out and System.err; these implementations
133       are not synchronized - you will need to take care of any
134       race conditions if you utilize these in more than one thread.
135       
136       * @param close if true, the current output and error streams
137       *   are closed before being reset (if they are not currently the
138       *   System output and error streams)
139       */
140        /*@ public normal_behavior
141          @   requires disable;
142          @   assignable \nothing;
143          @ also
144          @ public normal_behavior
145          @   requires !disable;
146          @   assignable System.out, System.err;
147          @   ensures System.out == pso
148          @        && System.err == pse;
149          @   ensures (close && \old(System.out) != pso) ==> \old(System.out).wasClosed;
150          @   ensures (close && \old(System.err) != pse) ==> \old(System.err).wasClosed;
151          @*/
152      static public void restoreStreams(boolean close) {
153        if (disable) return;
154        if (close) {
155          if (pso != System.out) System.out.close();
156          if (pse != System.err) System.err.close();
157        }
158        System.setOut(pso);
159        System.setErr(pse);
160      }
161      
162      
163      
164      /** Parses a string into arguments as if it were a command-line, using
165       the QuoteTokenizer to parse the tokens.
166       
167       @param s The String to parse
168       @return The input string parsed into command-line arguments
169       */
170      //@ ensures \nonnullelements(\result);
171      static public /*@ non_null */ String[] parseLine(/*@ non_null */String s) {
172        QuoteTokenizer q = new QuoteTokenizer(s);
173        java.util.ArrayList args = new java.util.ArrayList();
174        while (q.hasMoreTokens()) {
175          String a = q.nextToken();
176          args.add(a);
177        }
178        return (String[])args.toArray(new String[args.size()]);
179      }
180      
181      /** An enumerator that parses a string into tokens, according to the
182       rules a command-line would use.  White space separates tokens,
183       with double-quoted and single-quoted strings recognized.
184       */
185      // FIXME - does not handle escape sequences in strings, nor 
186      // quoted strings that are not the whole token, e.g.
187      // a b c+"asd"  should be three tokens, "a", "b", and  c+"asd" .
188      static public class QuoteTokenizer {
189        /** The String being tokenized */
190        /*@ non_null spec_public */ final private String ss;
191        
192        /** A char array representation of the String being tokenized */
193        /*@ non_null spec_public */ final private char[] cc;
194        
195        /** The position in the char array */
196        /*@ spec_public */ private int pos = 0;
197        /*@ invariant pos >= 0;
198          @ invariant pos <= cc.length;
199          @*/
200        
201        /** Initializes the tokenizer with the given String
202         * @param s the String to be tokenized
203         */
204        //@ modifies ss,cc;
205        //@ ensures s == ss;
206        public QuoteTokenizer(/*@ non_null */ String s) {
207          ss = s;
208          cc = s.toCharArray();
209        }
210    
211          /*@ public model boolean moreTokens;
212            @ public represents moreTokens <-
213            @       (\exists int i; pos <= i && i < cc.length;
214            @               Character.isWhitespace(cc[i]));
215            @*/
216    
217          /*@ public model boolean moreChar;
218            @ public represents moreChar <- pos < cc.length;
219            @*/
220    
221          /*@ public invariant_redundantly moreTokens ==> moreChar;
222            @ public invariant_redundantly 
223            @ moreChar && !Character.isWhitespace(cc[pos]) ==> moreTokens;
224            @*/
225    
226        /**
227         * @return true if there are more tokens to be returned
228         */
229          /*@ public normal_behavior
230            @ modifies pos;
231            @ ensures \result == moreChar;
232            @ ensures \result ==> !Character.isWhitespace(cc[pos]);
233            @ ensures_redundantly \result ==> moreTokens; */
234         /*+@ ensures \result == moreTokens;                // true but esc cannot prove
235            @ ensures moreTokens == \old(moreTokens);       // true but esc cannot prove
236            @*/
237        public boolean hasMoreTokens() {
238          while (pos < cc.length && Character.isWhitespace(cc[pos])) pos++;
239          return pos < cc.length;
240        }
241    
242        /**
243         * @return the next token if there is one, otherwise null
244         */
245          /*@ public normal_behavior
246            @  requires moreChar;
247            @  modifies pos;
248            @  ensures  pos > \old(pos);
249            @ also public normal_behavior
250            @  requires !moreChar;
251            @  modifies \nothing;
252            @  ensures \result == null;
253            @*/
254         /*+@ also
255            @ public normal_behavior
256            @   requires moreTokens;
257            @   modifies pos;
258            @   ensures \result != null;
259            @ also public normal_behavior
260            @   requires !moreTokens;
261            @   modifies pos;
262            @   ensures \result == null;
263            @*/
264        public String nextToken() {
265          String res = null;
266          while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos;
267          if (pos == cc.length) return res;
268          int start = pos;
269          if (cc[pos] == '"') {
270            ++pos;
271            while (pos < cc.length && cc[pos] != '"' ) ++pos;
272            if (cc[pos] == '"') ++pos;
273            res = ss.substring(start+1,pos-1);
274          } else if (cc[pos] == '\'') {
275            ++pos;
276            while (pos < cc.length && cc[pos] != '\'' ) ++pos;
277            if (cc[pos] == '\'') ++pos;
278            res = ss.substring(start+1,pos-1);
279          } else {
280            while (pos < cc.length && !Character.isWhitespace(cc[pos])) ++pos;
281            res = ss.substring(start,pos);
282          }
283          return res;
284        }
285      }
286      
287      /** Deletes the contents of a directory, including subdirectories.  
288       If the second argument is true, the directory itself is deleted as well.
289       
290       @param d The directory whose contents are deleted
291       @param removeDirectoryItself if true, the directory itself is deleted
292       
293       @return  false if something could not be deleted; 
294       true if everything was successfully deleted
295       */
296      static public boolean recursivelyRemoveDirectory(/*@ non_null */ File d, 
297          boolean removeDirectoryItself) {
298        if (!d.exists()) return true;
299        boolean success = true;
300        File[] fl = d.listFiles();
301        if (fl != null) {
302          for (int ff=0; ff<fl.length; ++ff) {
303            if (fl[ff].isDirectory()) {
304              if (!recursivelyRemoveDirectory(fl[ff],true)) 
305                success = false;
306            } else {
307              if (!fl[ff].delete()) success = false;
308            }
309          }
310        }
311        if (removeDirectoryItself) {
312          if (!d.delete()) success = false;
313        }
314        return success;
315      }
316      
317      /** Reads the contents of the file with the given name, returning a String.
318       This is an optimized version that uses the byte array provided and 
319       presumes that the file is shorter than the length of the array.
320       
321       @param filename              The file to be read
322       @param cb            A temporary byte array to speed reading
323       
324       @return                      The contents of the file
325       @throws java.io.IOException
326       */
327      // FIXME - can we check that the file is too long without losing the efficiency benefits?
328      static public /*@ non_null */ String readFile(/*@ non_null */ String filename, /*@ non_null */ byte[] cb) 
329      throws java.io.IOException {
330        int i = 0;
331        int j = 0;
332        java.io.FileInputStream f = null;
333        try {
334          f = new java.io.FileInputStream(filename);
335          while (j != -1) {
336            i = i + j;
337            j = f.read(cb,i,cb.length-i);
338          }
339        } finally {
340          if (f != null) f.close();
341        }
342        return new String(cb,0,i);
343      }
344      
345      /**
346       * Reads a file, returning a String containing the contents
347       * @param filename the file to be read
348       * @return the contents of the file as a String, or null if the
349       *      file could not be read
350       */
351      static public String readFileX(/*@ non_null */ String filename) {
352        try {
353          return readFile(filename);
354        } catch (Exception e ) {
355          System.out.println("Could not read file " + filename);
356          // FIXME - need a better way to report and catch errors
357        }
358        return null;
359      }
360      
361      /** Reads the contents of the file with the given name, returning a String. 
362       This version presumes the file is shorter than an internal buffer.
363       FIXME
364       
365       @param filename              The file to be read
366       @return                              The contents of the file
367       @throws IOException
368       */
369      static public /*@ non_null */ String readFile(/*@ non_null */ String filename) throws java.io.IOException {
370        StringBuffer sb = new StringBuffer(10000);
371        char[] cb = new char[10000];  // This hard-coded value can be anything;
372                    // smaller numbers will be less efficient since more reads
373                    // may result
374        int j = 0;
375        java.io.FileReader f = null;
376        try {
377          f = new java.io.FileReader(filename);
378          while (j != -1) {
379            j = f.read(cb,0,cb.length);
380            if (j != -1) sb.append(cb,0,j);
381          }
382        } finally {
383          if (f != null) f.close();
384        }
385        return sb.toString();
386      }
387      
388      /**
389       * Executes the static compile(String[]) method of the given class
390       * @param cls The class whose 'compile' method is be invoked
391       * @param args   The String[] argument of the method
392       * @return The standard output and error output of the invocation
393       */
394      //@ requires \nonnullelements(args);
395      static public String executeCompile(/*@ non_null */ Class cls, 
396                                          /*@ non_null */ String[] args) 
397        throws SecurityException, NoSuchMethodException
398      {
399        return executeMethod(cls,"compile",args);
400      }
401      
402      /** Finds and executes the method with the given name in the given class;
403       the method must have a single argument of type String[].  The 'args'
404       parameter is supplied to it as its argument.
405       * @param cls         The class whose method is to be invoked
406       * @param methodname The method to be invoked
407       * @param args                The argument of the method
408       * @return  The standard output and error output of the invocation
409       */
410      //@ requires \nonnullelements(args);
411      static private String executeMethod(/*@ non_null */ Class cls, 
412                                         /*@ non_null */ String methodname, 
413                                         /*@ non_null */ String[] args) 
414        throws SecurityException, NoSuchMethodException 
415      {
416        try {
417          Method method = cls.getMethod(methodname, new Class[] { String[].class });
418          return executeMethod(method,args);
419        } catch (NoSuchMethodException e) {
420          System.err.println("No method compile in class " + cls);  // FIXME - better error return needed
421          e.printStackTrace();
422          throw e; // new RuntimeException(e.toString());
423        }
424      }
425      
426      /** Calls the given method on the given String[] argument.  Any standard
427       output and error output is collected and returned as the String 
428       return value.
429       * @param method      The static method to be invoked
430       * @param args        The argument of the method
431       * @return            The standard output and error output of the method
432       */
433      //@ requires \nonnullelements(args);
434      static private String executeMethod(/*@ non_null */ Method method, 
435                                         /*@ non_null */ String[] args) {
436        try {
437            ByteArrayOutputStream ba = setStreams();
438            Object result = method.invoke(null,new Object[]{args});
439            // The following line might cause a cast error, but since b is not used it is comment out.
440            // boolean b = ((Boolean)result).booleanValue();
441            return ba.toString();
442        } catch (Exception e) {
443          Utils.restoreStreams(); // FIXME - see comment in TestFilesTestSuite.java
444          // Need the above restore before we try to print something
445          System.out.println(e.toString());  // FIXME - need better error handling
446        } finally {
447          Utils.restoreStreams();
448        }
449        return null;
450      }
451      
452      /** The suffix to append to create the golden output filename */
453      static private final String ORACLE_SUFFIX = "-expected";
454      
455      /** The suffix to append to create the actual output filename */
456      static private final String SAVED_SUFFIX = "-ckd";
457      
458      /** Compares the given string to the content of the given file using
459       a comparator that ignores platform differences in line-endings.
460       The method has the side effect of saving the string value in a file
461       for later comparison if the string and file are different, and making
462       sure that the actual output file (the -ckd file) is deleted if the
463       string and file are the same.  The expected output filename is the
464       rootname + "-expected"; the actual output filename is the rootname + "-ckd". 
465       *  
466       * @param s the String to compare
467       * @param rootname the prefix of the file name
468       * @return The Diff structure that contains the comparison
469       * @throws java.io.IOException
470       */
471      static public Diff compareStringToFile(/*@ non_null */ String s, String rootname) 
472                                                   throws java.io.IOException {
473        String ss = Utils.readFile(rootname+ORACLE_SUFFIX);
474        Diff df = new Diff( "expected", ss, "actual", s );
475        
476        if (!df.areDifferent()) {
477          // If the two strings match, the test succeeds and we make sure
478          // that there is no -ckd file to confuse anyone.
479          (new File(rootname+SAVED_SUFFIX)).delete();
480        } else {
481          // If the strings do not match, we save the actual string and 
482          // fail the test.
483          FileWriter f = null;
484          try {
485            f = new FileWriter(rootname+SAVED_SUFFIX);
486            f.write(s); 
487          } finally {
488            if (f != null) f.close(); 
489          }
490        }
491        return df;
492      }
493      
494      /** This deletes all files (in the current directory) whose
495       names match the given pattern in a regular-expression sense;
496       however, it is only implemented for patterns consisting of
497       characters and at most one '*', since I'm not going to rewrite
498       an RE library.
499       * @param pattern the pattern to match against filenames
500       */
501      static public void removeFiles(/*@ non_null */ String pattern) {
502        File[] list;
503        int k = pattern.indexOf("*");
504        if (k == -1) {
505          list = new File[] { new File(pattern) };
506        } else if (k == 0) {
507          final String s = pattern.substring(1);
508          FileFilter ff = new FileFilter() { 
509            public boolean accept(File f) { return f.isFile() && f.getName().endsWith(s); }
510          };
511          list = (new File(".")).listFiles(ff);
512        } else if (k+1 == pattern.length()) {
513          final String s = pattern.substring(0,k);
514          FileFilter ff = new FileFilter() { 
515            public boolean accept(File f) { return f.isFile() && f.getName().startsWith(s); }
516          };
517          list = (new File(".")).listFiles(ff);        
518        } else {
519          final String s = pattern.substring(0,k);
520          final String ss = pattern.substring(k+1);
521          final int j = pattern.length()-1;
522          FileFilter ff = new FileFilter() { 
523            public boolean accept(File f) { 
524              return  f.isFile() && 
525              f.getName().length() >= j && 
526              f.getName().startsWith(s) && 
527              f.getName().endsWith(ss); }
528          };
529          list = (new File(".")).listFiles(ff);        
530        }
531        
532        for (int i = 0; i<list.length; ++i) {
533          //System.out.println("DELETING " +list[i].getName());
534          list[i].delete();
535        }
536        
537      }
538    }