    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.10 2005/01/15 03:21:43 cok 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      //@ requires ps != null;
    056      //@ ensures System.out == ps && System.err == ps;
    057      static public void setStreams(PrintStream ps) {
    058        if (disable) return;
    059        pso.flush();
    060        pse.flush();
    061        System.setOut(ps);
    062        System.setErr(ps);
    063      }
    064
    065      /** Creates a new output stream (which is returned) and makes it
    066       the stream into which the standard and error outputs are captured.
    067
    068       @return an output stream into which standard and error output
    069       is captured
    070       */
    071      //@ ensures \result != null;
    072      static public ByteArrayOutputStream setStreams() {
    073        ByteArrayOutputStream ba = new ByteArrayOutputStream(10000);
    074        PrintStream ps = new PrintStream(ba);
    075        setStreams(ps);
    076        return ba;
    077      }
    078      // TODO - note the hard-coded size of the stream above.  It needs to be
    079      // variable, or at least be sure to capture overflows.  The size needs to
    080      // be large enough to hold the output of a test.
    081
    082      /** Restores System.out and System.err to the initial,
    083       system-defined values. It is ok to call this method
    084       even if setStreams has not been called.
    085       <p>
    086       Note that setStreams/restoreStreams operate on the global
    087       values of System.out and System.err; these implementations
    088       are not synchronized - you will need to take care of any
    089       race conditions if you utilize these in more than one thread.
    090       */
    091      //@ ensures System.out == pso && System.err == pse;
    092      static public void restoreStreams() {
    093        restoreStreams(false);
    094      }
    095
    096      /** Restores System.out and System.err to the initial,
    097       system-defined values. It is ok to call this method
    098       even if setStreams has not been called.
    099       <p>
    100       Note that setStreams/restoreStreams operate on the global
    101       values of System.out and System.err; these implementations
    102       are not synchronized - you will need to take care of any
    103       race conditions if you utilize these in more than one thread.
    104
    105       * @param close if true, the current output and error streams
    106       *   are closed before being reset (if they are not currently the
    107       *   System output and error streams)
    108       */
    109      static public void restoreStreams(boolean close) {
    110        if (disable) return;
    111        if (close) {
    112          if (pso != System.out) pso.close();
    113          if (pse != System.err) pse.close();
    114        }
    115        System.setOut(pso);
    116        System.setErr(pse);
    117      }
    118
    119
    120
    121      /** Parses a string into arguments as if it were a command-line, using
    122       the QuoteTokenizer to parse the tokens.
    123
    124       @param s The String to parse
    125       @return The input string parsed into command-line arguments
    126       */
    127      //@ requires s != null;
    128      //@ ensures \result != null;
    129      //@ ensures !\nonnullelements(\result);
    130      static public String[] parseLine(String s) {
    131        QuoteTokenizer q = new QuoteTokenizer(s);
    132        java.util.ArrayList args = new java.util.ArrayList();
    133        while (q.hasMoreTokens()) {
    134          String a = q.nextToken();
    135          args.add(a);
    136        }
    137        return (String[])args.toArray(new String[args.size()]);
    138      }
    139
    140      /** An enumerator that parses a string into tokens, according to the
    141       rules a command-line would use.  White space separates tokens,
    142       with double-quoted and single-quoted strings recognized.
    143       */
    144      // FIXME - does not handle escape sequences in strings, nor
    145      // quoted strings that are not the whole token, e.g.
    146      // a b c+"asd"  should be three tokens, "a", "b", and  c+"asd" .
    147      static public class QuoteTokenizer {
    148        /** The String being tokenized */
    149        /*@ non_null spec_public */ final private String ss;
    150
    151        /** A char array representation of the String being tokenized */
    152        /*@ non_null spec_public */ final private char[] cc;
    153
    154        /** The position in the char array */
    155        /*@ spec_public */
    156        private int pos = 0;        /*@ invariant pos >= 0;
    157                                        invariant pos <= cc.length;
    158                                     */
    159
    160        /** Initializes the tokenizer with the given String
    161         * @param s the String to be tokenized
    162         */
    163        //@ requires s != null;
    164        //@ modifies ss,cc;
    165        //@ ensures s == ss;
    166        public QuoteTokenizer(String s) {
    167          ss = s;
    168          cc = s.toCharArray();
    169        }
    170
    171        /*@
    172         ensures \result ==> (pos < cc.length);
    173         ensures \result == !(\forall int i; pos<=i && i < cc.length;
    174                                       Character.isWhitespace(cc[i]));
    175         model public pure boolean moreTokens();
    176         */
    177
    178        /**
    179         * @return true if there are more tokens to be returned
    180         */
    181        //@ modifies pos;
    182        //@ ensures \result == \old(moreTokens());
    183        //@ ensures \result ==> !Character.isWhitespace(cc[pos]);
    184        //@ ensures moreTokens() == \old(moreTokens());
    185        public boolean hasMoreTokens() {
    186          while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos;
    187          return pos < cc.length;
    188        }
    189
    190        /**
    191         * @return the next token if there is one, otherwise null
    192         */
    193        //@ public normal_behavior
    194        //@         requires moreTokens();
    195        //@   modifies pos;
    196        //@   ensures \result != null;
    197        //@ also public normal_behavior
    198        //@   requires !moreTokens();
    199        //@   modifies pos;
    200        //@   ensures \result == null;
    201        public String nextToken() {
    202          String res = null;
    203          while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos;
    204          if (pos == cc.length) return res;
    205          int start = pos;
    206          if (cc[pos] == '"') {
    207            ++pos;
    208            while (pos < cc.length && cc[pos] != '"' ) ++pos;
    209            if (cc[pos] == '"') ++pos;
    210            res = ss.substring(start+1,pos-1);
    211          } else if (cc[pos] == '\'') {
    212            ++pos;
    213            while (pos < cc.length && cc[pos] != '\'' ) ++pos;
    214            if (cc[pos] == '\'') ++pos;
    215            res = ss.substring(start+1,pos-1);
    216          } else {
    217            while (pos < cc.length && !Character.isWhitespace(cc[pos])) ++pos;
    218            res = ss.substring(start,pos);
    219          }
    220          return res;
    221        }
    222      }
    223
    224      /** Deletes the contents of a directory, including subdirectories.
    225       If the second argument is true, the directory itself is deleted as well.
    226
    227       @param d The directory whose contents are deleted
    228       @param removeDirectoryItself if true, the directory itself is deleted
    229
    230       @return  false if something could not be deleted;
    231       true if everything was successfully deleted
    232       */
    233      //@ requires d != null;
    234      static public boolean recursivelyRemoveDirectory(File d,
    235          boolean removeDirectoryItself) {
    236        if (!d.exists()) return true;
    237        boolean success = true;
    238        File[] fl = d.listFiles();
    239        if (fl != null) {
    240          for (int ff=0; ff<fl.length; ++ff) {
    241            if (fl[ff].isDirectory()) {
    242              if (!recursivelyRemoveDirectory(fl[ff],true))
    243                success = false;
    244            } else {
    245              if (!fl[ff].delete()) success = false;
    246            }
    247          }
    248        }
    249        if (removeDirectoryItself) {
    250          if (!d.delete()) success = false;
    251        }
    252        return success;
    253      }
    254
    255      /** Reads the contents of the file with the given name, returning a String.
    256       This is an optimized version that uses the byte array provided and
    257       presumes that the file is shorter than the length of the array.
    258
    259       @param filename              The file to be read
    260       @param cb            A temporary byte array to speed reading
    261
    262       @return                      The contents of the file
    263       @throws java.io.IOException
    264       */
    265      // FIXME - can we check that the file is too long without losing the efficiency benefits?
    266      //@ requires filename != null;
    267      //@ requires cb != null;
    268      //@ ensures \result != null;
    269      static public String readFile(String filename, byte[] cb)
    270      throws java.io.IOException {
    271        int i = 0;
    272        int j = 0;
    273        java.io.FileInputStream f = null;
    274        try {
    275          f = new java.io.FileInputStream(filename);
    276          while (j != -1) {
    277            i = i + j;
    278            j = f.read(cb,i,cb.length-i);
    279          }
    280        } finally {
    281          if (f != null) f.close();
    282        }
    283        return new String(cb,0,i);
    284      }
    285
    286      /**
    287       * Reads a file, returning a String containing the contents
    288       * @param filename the file to be read
    289       * @return the contents of the file as a String, or null if the
    290       *      file could not be read
    291       */
    292      //@ requires filename != null;
    293      static public String readFileX(String filename) {
    294        try {
    295          return readFile(filename);
    296        } catch (Exception e ) {
    297          System.out.println("Could not read file " + filename);
    298          // FIXME - need a better way to report and catch errors
    299        }
    300        return null;
    301      }
    302
    303      /** Reads the contents of the file with the given name, returning a String.
    304       This version presumes the file is shorter than an internal buffer.
    305       FIXME
    306
    307       @param filename              The file to be read
    308       @return                              The contents of the file
    309       @throws IOException
    310       */
    311      //@ requires filename != null;
    312      //@ ensures \result != null;
    313      static public String readFile(String filename) throws java.io.IOException {
    314        StringBuffer sb = new StringBuffer(10000);
    315        char[] cb = new char[10000];  // This hard-coded value can be anything;
    316                    // smaller numbers will be less efficient since more reads
    317                    // may result
    318        int j = 0;
    319        java.io.FileReader f = null;
    320        try {
    321          f = new java.io.FileReader(filename);
    322          while (j != -1) {
    323            j = f.read(cb,0,cb.length);
    324            if (j != -1) sb.append(cb,0,j);
    325          }
    326        } finally {
    327          if (f != null) f.close();
    328        }
    329        return sb.toString();
    330      }
    331
    332      /**
    333       * Executes the static compile(String[]) method of the given class
    334       * @param cls The class whose 'compile' method is be invoked
    335       * @param args   The String[] argument of the method
    336       * @return The standard output and error output of the invocation
    337       */
    338      //@ requires cls != null;
    339      //@ requires args != null;
    340      //@ requires !\nonnullelements(args);
    341      static public String executeCompile(Class cls, String[] args) {
    342        return executeMethod(cls,"compile",args);
    343      }
    344
    345      /** Finds and executes the method with the given name in the given class;
    346       the method must have a single argument of type String[].  The 'args'
    347       parameter is supplied to it as its argument.
    348       * @param cls         The class whose method is to be invoked
    349       * @param methodname The method to be invoked
    350       * @param args                The argument of the method
    351       * @return  The standard output and error output of the invocation
    352       */
    353      //@ requires cls != null;
    354      //@ requires methodname != null;
    355      //@ requires args != null;
    356      //@ requires !\nonnullelements(args);
    357      static public String executeMethod(Class cls, String methodname, String[] args) {
    358        try {
    359          Method method = cls.getMethod(methodname, new Class[] { String[].class });
    360          return executeMethod(method,args);
    361        } catch (NoSuchMethodException e) {
    362          System.out.println("No method compile in class " + cls);  // FIXME - better error return needed
    363          e.printStackTrace();
    364          throw new RuntimeException(e.toString());
    365        }
    366      }
    367
    368      /** Calls the given method on the given String[] argument.  Any standard
    369       output and error output is collected and returned as the String
    370       return value.
    371       * @param method      The static method to be invoked
    372       * @param args        The argument of the method
    373       * @return            The standard output and error output of the method
    374       */
    375      //@ requires method != null;
    376      //@ requires args != null;
    377      //@ requires !\nonnullelements(args);
    378      static public String executeMethod(Method method, String[] args) {
    379        try {
    380          ByteArrayOutputStream ba = new ByteArrayOutputStream(10000); // FIXME - hardcoded size
    381          PrintStream ps = new PrintStream(ba);
    382          Utils.setStreams(ps);
    383          boolean b = ((Boolean)(method.invoke(null,new Object[]{args}))).booleanValue();
    384          ps.close();
    385          String s = ba.toString();
    386          return s;
    387        } catch (Exception e) {
    388          Utils.restoreStreams(); // FIXME - see comment in TestFilesTestSuite.java
    389          // Need the above restore before we try to print something
    390          System.out.println(e.toString());  // FIXME - need better error handling
    391        } finally {
    392          Utils.restoreStreams();
    393        }
    394        return null;
    395      }
    396
    397      /** The suffix to append to create the golden output filename */
    398      static private final String ORACLE_SUFFIX = "-expected";
    399
    400      /** The suffix to append to create the actual output filename */
    401      static private final String SAVED_SUFFIX = "-ckd";
    402
    403      /** Compares the given string to the content of the given file using
    404       a comparator that ignores platform differences in line-endings.
    405       The method has the side effect of saving the string value in a file
    406       for later comparison if the string and file are different, and making
    407       sure that the actual output file (the -ckd file) is deleted if the
    408       string and file are the same.  The expected output filename is the
    409       rootname + "-expected"; the actual output filename is the rootname + "-ckd".
    410       *
    411       * @param s the String to compare
    412       * @param rootname the prefix of the file name
    413       * @return The Diff structure that contains the comparison
    414       * @throws java.io.IOException
    415       */
    416      static public Diff compareStringToFile(String s, String rootname)
    417                                                   throws java.io.IOException {
    418        String ss = Utils.readFile(rootname+ORACLE_SUFFIX);
    419        Diff df = new Diff( "expected", ss, "actual", s );
    420
    421        if (!df.areDifferent()) {
    422          // If the two strings match, the test succeeds and we make sure
    423          // that there is no -ckd file to confuse anyone.
    424          (new File(rootname+SAVED_SUFFIX)).delete();
    425        } else {
    426          // If the strings do not match, we save the actual string and
    427          // fail the test.
    428          FileWriter f = null;
    429          try {
    430            f = new FileWriter(rootname+SAVED_SUFFIX);
    431            f.write(s);
    432          } finally {
    433            if (f != null) f.close();
    434          }
    435        }
    436        return df;
    437      }
    438
    439      /** This deletes all files (in the current directory) whose
    440       names match the given pattern in a regular-expression sense;
    441       however, it is only implemented for patterns consisting of
    442       characters and at most one '*', since I'm not going to rewrite
    443       an RE library.
    444       * @param pattern the pattern to match against filenames
    445       */
    446      //@ requires pattern != null;
    447      static public void removeFiles(String pattern) {
    448        File[] list;
    449        int k = pattern.indexOf("*");
    450        if (k == -1) {
    451          list = new File[] { new File(pattern) };
    452        } else if (k == 0) {
    453          final String s = pattern.substring(1);
    454          FileFilter ff = new FileFilter() {
    455            public boolean accept(File f) { return f.isFile() && f.getName().endsWith(s); }
    456          };
    457          list = (new File(".")).listFiles(ff);
    458        } else if (k+1 == pattern.length()) {
    459          final String s = pattern.substring(0,k);
    460          FileFilter ff = new FileFilter() {
    461            public boolean accept(File f) { return f.isFile() && f.getName().startsWith(s); }
    462          };
    463          list = (new File(".")).listFiles(ff);
    464        } else {
    465          final String s = pattern.substring(0,k);
    466          final String ss = pattern.substring(k+1);
    467          final int j = pattern.length()-1;
    468          FileFilter ff = new FileFilter() {
    469            public boolean accept(File f) {
    470              return  f.isFile() &&
    471              f.getName().length() >= j &&
    472              f.getName().startsWith(s) &&
    473              f.getName().endsWith(ss); }
    474          };
    475          list = (new File(".")).listFiles(ff);
    476        }
    477
    478        for (int i = 0; i<list.length; ++i) {
    479          //System.out.println("DELETING " +list[i].getName());
    480          list[i].delete();
    481        }
    482
    483      }
    484    }