001    // If you edit this file, you must also edit its tests.
002    // For tests of this and the entire plume package, see class TestPlume.
003    
004    package daikon.util;
005    
006    import java.io.*;
007    import java.util.*;
008    import java.util.zip.*;
009    import java.lang.reflect.*;
010    // import Assert;
011    
012    /** Utility functions that do not belong elsewhere in the plume package. */
013    public final class UtilMDE {
014      private UtilMDE() { throw new Error("do not instantiate"); }
015    
016      @SuppressWarnings("nullness") // line.separator property always exists
017      private static final String lineSep = System.getProperty("line.separator");
018    
019      ///////////////////////////////////////////////////////////////////////////
020      /// Array
021      ///
022    
023      // For arrays, see ArraysMDE.java.
024    
025      ///////////////////////////////////////////////////////////////////////////
026      /// BitSet
027      ///
028    
029      /**
030       * Returns true if the cardinality of the intersection of the two
031       * BitSets is at least the given value.
032       **/
033      public static boolean intersectionCardinalityAtLeast(BitSet a, BitSet b, int i) {
034        // Here are three implementation strategies to determine the
035        // cardinality of the intersection:
036        // 1. a.clone().and(b).cardinality()
037        // 2. do the above, but copy only a subset of the bits initially -- enough
038        //    that it should exceed the given number -- and if that fails, do the
039        //    whole thing.  Unfortunately, bits.get(int, int) isn't optimized
040        //    for the case where the indices line up, so I'm not sure at what
041        //    point this approach begins to dominate #1.
042        // 3. iterate through both sets with nextSetBit()
043    
044        int size = Math.min(a.length(), b.length());
045        if (size > 10*i) {
046          // The size is more than 10 times the limit.  So first try processing
047          // just a subset of the bits (4 times the limit).
048          BitSet intersection = a.get(0, 4*i);
049          intersection.and(b);
050          if (intersection.cardinality() >= i) {
051            return true;
052          }
053        }
054        return (intersectionCardinality(a, b) >= i);
055      }
056    
057      /**
058       * Returns true if the cardinality of the intersection of the two
059       * BitSets is at least the given value.
060       **/
061      public static boolean intersectionCardinalityAtLeast(BitSet a, BitSet b, BitSet c, int i) {
062        // See comments in intersectionCardinalityAtLeast(BitSet, BitSet, int).
063        // This is a copy of that.
064    
065        int size = Math.min(a.length(), b.length());
066        size = Math.min(size, c.length());
067        if (size > 10*i) {
068          // The size is more than 10 times the limit.  So first try processing
069          // just a subset of the bits (4 times the limit).
070          BitSet intersection = a.get(0, 4*i);
071          intersection.and(b);
072          intersection.and(c);
073          if (intersection.cardinality() >= i) {
074            return true;
075          }
076        }
077        return (intersectionCardinality(a, b, c) >= i);
078      }
079    
080      /** Returns the cardinality of the intersection of the two BitSets. **/
081      public static int intersectionCardinality(BitSet a, BitSet b) {
082        BitSet intersection = (BitSet) a.clone();
083        intersection.and(b);
084        return intersection.cardinality();
085      }
086    
087      /** Returns the cardinality of the intersection of the three BitSets. **/
088      public static int intersectionCardinality(BitSet a, BitSet b, BitSet c) {
089        BitSet intersection = (BitSet) a.clone();
090        intersection.and(b);
091        intersection.and(c);
092        return intersection.cardinality();
093      }
094    
095    
096      ///////////////////////////////////////////////////////////////////////////
097      /// BufferedFileReader
098      ///
099    
100    
101      // Convenience methods for creating InputStreams, Readers, BufferedReaders, and LineNumberReaders.
102    
103      /**
104       * Returns an InputStream for the file, accounting for the possibility
105       * that the file is compressed.
106       * (A file whose name ends with ".gz" is treated as compressed.)
107       * <p>
108       * Warning: The "gzip" program writes and reads files containing
109       * concatenated gzip files.  As of Java 1.4, Java reads
110       * just the first one:  it silently discards all characters (including
111       * gzipped files) after the first gzipped file.
112       */
113      public static InputStream fileInputStream(File file) throws IOException {
114        InputStream in;
115        if (file.getName().endsWith(".gz")) {
116          in = new GZIPInputStream(new FileInputStream(file));
117        } else {
118          in = new FileInputStream(file);
119        }
120        return in;
121      }
122    
123      /**
124       * Returns a Reader for the file, accounting for the possibility
125       * that the file is compressed.
126       * (A file whose name ends with ".gz" is treated as compressed.)
127       * <p>
128       * Warning: The "gzip" program writes and reads files containing
129       * concatenated gzip files.  As of Java 1.4, Java reads
130       * just the first one:  it silently discards all characters (including
131       * gzipped files) after the first gzipped file.
132       **/
133      public static InputStreamReader fileReader(String filename) throws FileNotFoundException, IOException {
134        // return fileReader(filename, "ISO-8859-1");
135        return fileReader(new File(filename), null);
136      }
137    
138      /**
139       * Returns a Reader for the file, accounting for the possibility
140       * that the file is compressed.
141       * (A file whose name ends with ".gz" is treated as compressed.)
142       * <p>
143       * Warning: The "gzip" program writes and reads files containing
144       * concatenated gzip files.  As of Java 1.4, Java reads
145       * just the first one:  it silently discards all characters (including
146       * gzipped files) after the first gzipped file.
147       **/
148      public static InputStreamReader fileReader(File file) throws FileNotFoundException, IOException {
149        return fileReader(file, null);
150      }
151    
152    
153      /**
154       * Returns a Reader for the file, accounting for the possibility
155       * that the file is compressed.
156       * (A file whose name ends with ".gz" is treated as compressed.)
157       * @param charsetName may be null, or the name of a Charset
158       * <p>
159       * Warning: The "gzip" program writes and reads files containing
160       * concatenated gzip files.  As of Java 1.4, Java reads
161       * just the first one:  it silently discards all characters (including
162       * gzipped files) after the first gzipped file.
163       **/
164      public static InputStreamReader fileReader(File file, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
165        InputStream in = new FileInputStream(file);
166        InputStreamReader file_reader;
167        if (charsetName == null) {
168          file_reader = new InputStreamReader(in);
169        } else {
170          file_reader = new InputStreamReader(in, charsetName);
171        }
172        return file_reader;
173      }
174    
175      /**
176       * Returns a BufferedReader for the file, accounting for the possibility
177       * that the file is compressed.
178       * (A file whose name ends with ".gz" is treated as compressed.)
179       * <p>
180       * Warning: The "gzip" program writes and reads files containing
181       * concatenated gzip files.  As of Java 1.4, Java reads
182       * just the first one:  it silently discards all characters (including
183       * gzipped files) after the first gzipped file.
184       **/
185      public static BufferedReader bufferedFileReader(String filename) throws FileNotFoundException, IOException {
186        return bufferedFileReader(new File(filename));
187      }
188    
189      /**
190       * Returns a BufferedReader for the file, accounting for the possibility
191       * that the file is compressed.
192       * (A file whose name ends with ".gz" is treated as compressed.)
193       * <p>
194       * Warning: The "gzip" program writes and reads files containing
195       * concatenated gzip files.  As of Java 1.4, Java reads
196       * just the first one:  it silently discards all characters (including
197       * gzipped files) after the first gzipped file.
198       **/
199      public static BufferedReader bufferedFileReader(File file) throws FileNotFoundException, IOException {
200        return(bufferedFileReader(file, null));
201      }
202    
203      /**
204       * Returns a BufferedReader for the file, accounting for the possibility
205       * that the file is compressed.
206       * (A file whose name ends with ".gz" is treated as compressed.)
207       * <p>
208       * Warning: The "gzip" program writes and reads files containing
209       * concatenated gzip files.  As of Java 1.4, Java reads
210       * just the first one:  it silently discards all characters (including
211       * gzipped files) after the first gzipped file.
212       **/
213      public static BufferedReader bufferedFileReader(String filename, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
214        return bufferedFileReader(new File(filename), charsetName);
215      }
216    
217      /**
218       * Returns a BufferedReader for the file, accounting for the possibility
219       * that the file is compressed.
220       * (A file whose name ends with ".gz" is treated as compressed.)
221       * <p>
222       * Warning: The "gzip" program writes and reads files containing
223       * concatenated gzip files.  As of Java 1.4, Java reads
224       * just the first one:  it silently discards all characters (including
225       * gzipped files) after the first gzipped file.
226       **/
227      public static BufferedReader bufferedFileReader(File file, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
228        Reader file_reader = fileReader(file, charsetName);
229        return new BufferedReader(file_reader);
230      }
231    
232    
233      /**
234       * Returns a LineNumberReader for the file, accounting for the possibility
235       * that the file is compressed.
236       * (A file whose name ends with ".gz" is treated as compressed.)
237       * <p>
238       * Warning: The "gzip" program writes and reads files containing
239       * concatenated gzip files.  As of Java 1.4, Java reads
240       * just the first one:  it silently discards all characters (including
241       * gzipped files) after the first gzipped file.
242       **/
243      public static LineNumberReader lineNumberFileReader(String filename) throws FileNotFoundException, IOException {
244        return lineNumberFileReader(new File(filename));
245      }
246    
247      /**
248       * Returns a LineNumberReader for the file, accounting for the possibility
249       * that the file is compressed.
250       * (A file whose name ends with ".gz" is treated as compressed.)
251       * <p>
252       * Warning: The "gzip" program writes and reads files containing
253       * concatenated gzip files.  As of Java 1.4, Java reads
254       * just the first one:  it silently discards all characters (including
255       * gzipped files) after the first gzipped file.
256       **/
257      public static LineNumberReader lineNumberFileReader(File file) throws FileNotFoundException, IOException {
258        Reader file_reader;
259        if (file.getName().endsWith(".gz")) {
260          file_reader = new InputStreamReader(new GZIPInputStream(new FileInputStream(file)),
261                                              "ISO-8859-1");
262        } else {
263          file_reader = new InputStreamReader(new FileInputStream(file),
264                                              "ISO-8859-1");
265        }
266        return new LineNumberReader(file_reader);
267      }
268    
269      /**
270       * Returns a BufferedWriter for the file, accounting for the possibility
271       * that the file is compressed.
272       * (A file whose name ends with ".gz" is treated as compressed.)
273       * <p>
274       * Warning: The "gzip" program writes and reads files containing
275       * concatenated gzip files.  As of Java 1.4, Java reads
276       * just the first one:  it silently discards all characters (including
277       * gzipped files) after the first gzipped file.
278       **/
279      public static BufferedWriter bufferedFileWriter(String filename) throws IOException {
280        return bufferedFileWriter (filename, false);
281      }
282    
283      /**
284       * Returns a BufferedWriter for the file, accounting for the possibility
285       * that the file is compressed.
286       * (A file whose name ends with ".gz" is treated as compressed.)
287       * <p>
288       * Warning: The "gzip" program writes and reads files containing
289       * concatenated gzip files.  As of Java 1.4, Java reads
290       * just the first one:  it silently discards all characters (including
291       * gzipped files) after the first gzipped file.
292       * @param append if true, the resulting BufferedWriter appends to the end
293       * of the file instead of the beginning.
294       **/
295      public static BufferedWriter bufferedFileWriter(String filename, boolean append) throws IOException {
296        Writer file_writer;
297        if (filename.endsWith(".gz")) {
298          file_writer = new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(filename, append)));
299        } else {
300          file_writer = new FileWriter(filename, append);
301        }
302        return new BufferedWriter(file_writer);
303      }
304    
305    
306      /** @deprecated use bufferedFileReader (note lowercase first letter) */
307      @Deprecated // since June 2005
308      public static BufferedReader BufferedFileReader(String filename) throws FileNotFoundException, IOException {
309        return bufferedFileReader(filename);
310      }
311      /** @deprecated use lineNumberFileReader (note lowercase first letter) */
312      @Deprecated // since June 2005
313      public static LineNumberReader LineNumberFileReader(String filename) throws FileNotFoundException, IOException {
314        return lineNumberFileReader(filename);
315      }
316      /** @deprecated use lineNumberFileReader (note lowercase first letter) */
317      @Deprecated // since June 2005
318      public static LineNumberReader LineNumberFileReader(File file) throws FileNotFoundException, IOException {
319        return lineNumberFileReader(file);
320      }
321      /** @deprecated use bufferedFileWriter (note lowercase first letter) */
322      @Deprecated // since June 2005
323      public static BufferedWriter BufferedFileWriter(String filename) throws IOException {
324        return bufferedFileWriter(filename);
325      }
326      /** @deprecated use bufferedFileWriter (note lowercase first letter) */
327      @Deprecated // since June 2005
328      public static BufferedWriter BufferedFileWriter(String filename, boolean append) throws IOException {
329        return bufferedFileWriter(filename, append);
330      }
331    
332    
333      ///////////////////////////////////////////////////////////////////////////
334      /// Class
335      ///
336    
337      private static HashMap<String,Class<?>> primitiveClasses = new HashMap<String,Class<?>>(8);
338      static {
339        primitiveClasses.put("boolean", Boolean.TYPE);
340        primitiveClasses.put("byte", Byte.TYPE);
341        primitiveClasses.put("char", Character.TYPE);
342        primitiveClasses.put("double", Double.TYPE);
343        primitiveClasses.put("float", Float.TYPE);
344        primitiveClasses.put("int", Integer.TYPE);
345        primitiveClasses.put("long", Long.TYPE);
346        primitiveClasses.put("short", Short.TYPE);
347      }
348    
349      /**
350       * Like @link{Class.forName(String)}, but works when the string
351       * represents a primitive type, too.  If the original name can't
352       * be found, it also tries looking for the name with the last '.'
353       * changed to a dollar sign ($).  This accounts for inner classes
354       * which are sometimes separated with '.'.
355       **/
356      public static Class<?> classForName(String className) throws ClassNotFoundException {
357        Class<?> result = primitiveClasses.get(className);
358        if (result != null)
359          return result;
360        else {
361          try {
362            return Class.forName(className);
363          } catch (ClassNotFoundException e) {
364            int pos = className.lastIndexOf('.');
365            String inner_name = className.substring (0, pos) + "$"
366              + className.substring (pos+1);
367            try {
368              return Class.forName (inner_name);
369            } catch (ClassNotFoundException ee) {
370              throw e;
371            }
372          }
373        }
374      }
375    
376      private static HashMap<String,String> primitiveClassesJvm = new HashMap<String,String>(8);
377      static {
378        primitiveClassesJvm.put("boolean", "Z");
379        primitiveClassesJvm.put("byte", "B");
380        primitiveClassesJvm.put("char", "C");
381        primitiveClassesJvm.put("double", "D");
382        primitiveClassesJvm.put("float", "F");
383        primitiveClassesJvm.put("int", "I");
384        primitiveClassesJvm.put("long", "J");
385        primitiveClassesJvm.put("short", "S");
386      }
387    
388      /**
389       * Convert a fully-qualified classname from Java format to JVML format.
390       * For example, convert "java.lang.Object[]" to "[Ljava/lang/Object;".
391       **/
392      public static String classnameToJvm(String classname) {
393        int dims = 0;
394        while (classname.endsWith("[]")) {
395          dims++;
396          classname = classname.substring(0, classname.length()-2);
397        }
398        String result = primitiveClassesJvm.get(classname);
399        if (result == null) {
400          result = "L" + classname + ";";
401        }
402        for (int i=0; i<dims; i++) {
403          result = "[" + result;
404        }
405        return result.replace('.', '/');
406      }
407    
408      /**
409       * Convert a primitive java type name (e.g., "int", "double", etc.) to
410       * the single character JVM name (e.g., "I", "D", etc.).
411       * Throws IllegalArgumentException if primitive_name is not a valid name.
412       */
413      public static String primitive_name_to_jvm (String primitive_name) {
414        String result = primitiveClassesJvm.get (primitive_name);
415        if (result == null) {
416          throw new IllegalArgumentException("Not the name of a primitive type: " + primitive_name);
417        }
418        return result;
419      }
420    
421      /**
422       * Convert a fully-qualified argument list from Java format to JVML format.
423       * For example, convert "(java.lang.Integer[], int, java.lang.Integer[][])"
424       * to "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)".
425       **/
426      public static String arglistToJvm(String arglist) {
427        if (! (arglist.startsWith("(") && arglist.endsWith(")"))) {
428          throw new Error("Malformed arglist: " + arglist);
429        }
430        String result = "(";
431        String comma_sep_args = arglist.substring(1, arglist.length()-1);
432        StringTokenizer args_tokenizer
433          = new StringTokenizer(comma_sep_args, ",", false);
434        for ( ; args_tokenizer.hasMoreTokens(); ) {
435          String arg = args_tokenizer.nextToken().trim();
436          result += classnameToJvm(arg);
437        }
438        result += ")";
439        // System.out.println("arglistToJvm: " + arglist + " => " + result);
440        return result;
441      }
442    
443      private static HashMap<String,String> primitiveClassesFromJvm = new HashMap<String,String>(8);
444      static {
445        primitiveClassesFromJvm.put("Z", "boolean");
446        primitiveClassesFromJvm.put("B", "byte");
447        primitiveClassesFromJvm.put("C", "char");
448        primitiveClassesFromJvm.put("D", "double");
449        primitiveClassesFromJvm.put("F", "float");
450        primitiveClassesFromJvm.put("I", "int");
451        primitiveClassesFromJvm.put("J", "long");
452        primitiveClassesFromJvm.put("S", "short");
453      }
454    
455      // does not convert "V" to "void".  Should it?
456      /**
457       * Convert a classname from JVML format to Java format.
458       * For example, convert "[Ljava/lang/Object;" to "java.lang.Object[]".
459       **/
460      public static String classnameFromJvm(String classname) {
461        int dims = 0;
462        while (classname.startsWith("[")) {
463          dims++;
464          classname = classname.substring(1);
465        }
466        String result;
467        if (classname.startsWith("L") && classname.endsWith(";")) {
468          result = classname.substring(1, classname.length() - 1);
469        } else {
470          result = primitiveClassesFromJvm.get(classname);
471          if (result == null) {
472            throw new Error("Malformed base class: " + classname);
473          }
474        }
475        for (int i=0; i<dims; i++) {
476          result += "[]";
477        }
478        return result.replace('/', '.');
479      }
480    
481      /**
482       * Convert an argument list from JVML format to Java format.
483       * For example, convert "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)"
484       * to "(java.lang.Integer[], int, java.lang.Integer[][])".
485       **/
486      public static String arglistFromJvm(String arglist) {
487        if (! (arglist.startsWith("(") && arglist.endsWith(")"))) {
488          throw new Error("Malformed arglist: " + arglist);
489        }
490        String result = "(";
491        int pos = 1;
492        while (pos < arglist.length()-1) {
493          if (pos > 1)
494            result += ", ";
495          int nonarray_pos = pos;
496          while (arglist.charAt(nonarray_pos) == '[') {
497            nonarray_pos++;
498          }
499          char c = arglist.charAt(nonarray_pos);
500          if (c == 'L') {
501            int semi_pos = arglist.indexOf(";", nonarray_pos);
502            result += classnameFromJvm(arglist.substring(pos, semi_pos+1));
503            pos = semi_pos + 1;
504          } else {
505            String maybe = classnameFromJvm(arglist.substring(pos, nonarray_pos+1));
506            if (maybe == null) {
507              // return null;
508              throw new Error("Malformed arglist: " + arglist);
509            }
510            result += maybe;
511            pos = nonarray_pos+1;
512          }
513        }
514        return result + ")";
515      }
516    
517    
518      ///////////////////////////////////////////////////////////////////////////
519      /// ClassLoader
520      ///
521    
522      /**
523       * This class has no purpose but to define loadClassFromFile.
524       * ClassLoader.defineClass is protected, so I subclass ClassLoader in
525       * order to call defineClass.
526       **/
527      private static class PromiscuousLoader extends ClassLoader {
528        public Class<?> loadClassFromFile(String className, String pathname) throws FileNotFoundException, IOException {
529          FileInputStream fi = new FileInputStream(pathname);
530          int numbytes = fi.available();
531          byte[] classBytes = new byte[numbytes];
532          fi.read(classBytes);
533          fi.close();
534          Class<?> return_class = defineClass(className, classBytes, 0, numbytes);
535          resolveClass(return_class);
536          return return_class;
537        }
538      }
539    
540      private static PromiscuousLoader thePromiscuousLoader = new PromiscuousLoader();
541    
542      /**
543       * @param pathname the pathname of a .class file
544       * @return a Java Object corresponding to the Class defined in the .class file
545       **/
546      public static Class<?> loadClassFromFile(String className, String pathname) throws FileNotFoundException, IOException {
547        return thePromiscuousLoader.loadClassFromFile(className, pathname);
548      }
549    
550    
551      ///////////////////////////////////////////////////////////////////////////
552      /// Classpath
553      ///
554    
555      // Perhaps abstract out the simpler addToPath from this
556      /** Add the directory to the system classpath. */
557      public static void addToClasspath(String dir) {
558        // If the dir isn't on CLASSPATH, add it.
559        String pathSep = System.getProperty("path.separator");
560        // what is the point of the "replace()" call?
561        String cp = System.getProperty("java.class.path",".").replace('\\', '/');
562        StringTokenizer tokenizer = new StringTokenizer(cp, pathSep, false);
563        boolean found = false;
564        while (tokenizer.hasMoreTokens() && !found) {
565          found = tokenizer.nextToken().equals(dir);
566        }
567        if (!found) {
568          System.setProperty("java.class.path", dir + pathSep + cp);
569        }
570      }
571    
572    
573      ///////////////////////////////////////////////////////////////////////////
574      /// File
575      ///
576    
577    
578      /** Count the number of lines in the specified file **/
579      public static long count_lines(String filename) throws IOException {
580        LineNumberReader reader = UtilMDE.lineNumberFileReader(filename);
581        long count = 0;
582        while (reader.readLine() != null)
583          count++;
584        return count;
585      }
586    
587      /** Tries to infer the line separator used in a file. **/
588      public static String inferLineSeparator(String filename) throws IOException {
589        return inferLineSeparator(new File(filename));
590      }
591    
592      /** Tries to infer the line separator used in a file. **/
593      public static String inferLineSeparator(File filename) throws IOException {
594        BufferedReader r = UtilMDE.bufferedFileReader(filename);
595        int unix = 0;
596        int dos = 0;
597        int mac = 0;
598        while (true) {
599          String s = r.readLine();
600          if (s == null) {
601            break;
602          }
603          if (s.endsWith("\r\n")) {
604            dos++;
605          } else if (s.endsWith("\r")) {
606            mac++;
607          } else if (s.endsWith("\n")) {
608            unix++;
609          } else {
610            // This can happen only if the last line is not terminated.
611          }
612        }
613        if ((dos > mac && dos > unix)
614            || (lineSep.equals("\r\n") && dos >= unix && dos >= mac)) {
615          return "\r\n";
616        }
617        if ((mac > dos && mac > unix)
618            || (lineSep.equals("\r") && mac >= dos && mac >= unix)) {
619          return "\n";
620        }
621        if ((unix > dos && unix > mac)
622            || (lineSep.equals("\n") && unix >= dos && unix >= mac)) {
623          return "\n";
624        }
625        // The two non-preferred line endings are tied and have more votes than
626        // the preferred line ending.  Give up.
627        return lineSep;
628      }
629    
630    
631    
632      /**
633       * Returns true iff files have the same contents.
634       */
635      public static boolean equalFiles(String file1, String file2) {
636        return equalFiles(file1, file2, false);
637      }
638    
639      /**
640       * Returns true iff files have the same contents.
641       * @param trimLines if true, call String.trim on each line before comparing
642       */
643      public static boolean equalFiles(String file1, String file2, boolean trimLines) {
644        try {
645          LineNumberReader reader1 = UtilMDE.lineNumberFileReader(file1);
646          LineNumberReader reader2 = UtilMDE.lineNumberFileReader(file2);
647          String line1 = reader1.readLine();
648          String line2 = reader2.readLine();
649          while (line1 != null && line2 != null) {
650            if (trimLines) {
651              line1 = line1.trim();
652              line2 = line2.trim();
653            }
654            if (! (line1.equals(line2))) {
655              return false;
656            }
657            line1 = reader1.readLine();
658            line2 = reader2.readLine();
659          }
660          if (line1 == null && line2 == null) {
661            return true;
662          }
663          return false;
664        } catch (IOException e) {
665            throw new RuntimeException(e);
666          }
667      }
668    
669    
670      /**
671       * Returns true
672       *  if the file exists and is writable, or
673       *  if the file can be created.
674       **/
675      public static boolean canCreateAndWrite(File file) {
676        if (file.exists()) {
677          return file.canWrite();
678        } else {
679          File directory = file.getParentFile();
680          if (directory == null) {
681            directory = new File(".");
682          }
683          // Does this test need "directory.canRead()" also?
684          return directory.canWrite();
685        }
686    
687        /// Old implementation; is this equivalent to the new one, above??
688        // try {
689        //   if (file.exists()) {
690        //     return file.canWrite();
691        //   } else {
692        //     file.createNewFile();
693        //     file.delete();
694        //     return true;
695        //   }
696        // } catch (IOException e) {
697        //   return false;
698        // }
699      }
700    
701    
702      ///
703      /// Directories
704      ///
705    
706      /**
707       * Creates an empty directory in the default temporary-file directory,
708       * using the given prefix and suffix to generate its name. For example
709       * calling createTempDir("myPrefix", "mySuffix") will create the following
710       * directory: temporaryFileDirectory/myUserName/myPrefix_someString_suffix.
711       * someString is internally generated to ensure no temporary files of the
712       * same name are generated.
713       * @param prefix The prefix string to be used in generating the file's
714       *  name; must be at least three characters long
715       * @param suffix The suffix string to be used in generating the file's
716       *  name; may be null, in which case the suffix ".tmp" will be used Returns:
717       *  An abstract pathname denoting a newly-created empty file
718       * @throws IllegalArgumentException If the prefix argument contains fewer
719       *  than three characters
720       * @throws IOException If a file could not be created
721       * @throws SecurityException If a security manager exists and its
722       *  SecurityManager.checkWrite(java.lang.String) method does not allow a
723       *  file to be created
724       * @see java.io.File#createTempFile(String, String, File)
725       **/
726      public static File createTempDir(String prefix, String suffix)
727        throws IOException {
728        String fs = File.separator;
729        String path = System.getProperty("java.io.tmpdir") + fs +
730          System.getProperty("user.name") + fs;
731        File pathFile =  new File(path);
732        pathFile.mkdirs();
733        File tmpfile = File.createTempFile(prefix + "_", "_", pathFile);
734        String tmpDirPath = tmpfile.getPath() + suffix;
735        tmpfile.delete();
736        File tmpDir = new File(tmpDirPath);
737        tmpDir.mkdirs();
738        return tmpDir;
739      }
740    
741    
742      /**
743       * Deletes the directory at dirName and all its files.
744       * Fails if dirName has any subdirectories.
745       */
746      public static void deleteDir(String dirName) {
747        deleteDir(new File(dirName));
748      }
749    
750      /**
751       * Deletes the directory at dirName and all its files.
752       * Fails if dirName has any subdirectories.
753       */
754      public static void deleteDir(File dir) {
755        File[] files = dir.listFiles();
756        if (files == null) {
757          return;
758        }
759        for (int i = 0; i < files.length; i++) {
760          files[i].delete();
761        }
762        dir.delete();
763      }
764    
765    
766      ///
767      /// File names (aka filenames)
768      ///
769    
770      // Someone must have already written this.  Right?
771    
772      // Deals with exactly one "*" in name.
773      public static final class WildcardFilter implements FilenameFilter {
774        String prefix;
775        String suffix;
776        public WildcardFilter(String filename) {
777          int astloc = filename.indexOf("*");
778          if (astloc == -1)
779            throw new Error("No asterisk in wildcard argument: " + filename);
780          prefix = filename.substring(0, astloc);
781          suffix = filename.substring(astloc+1);
782          if (filename.indexOf("*") != -1)
783            throw new Error("Multiple asterisks in wildcard argument: " + filename);
784        }
785        public boolean accept(File dir, String name) {
786          return name.startsWith(prefix) && name.endsWith(suffix);
787        }
788      }
789    
790      @SuppressWarnings("nullness") // user.home property always exists
791      static final /*@NonNull*/ String userHome = System.getProperty ("user.home");
792    
793      /**
794       * Does tilde expansion on a file name (to the user's home directory).
795       */
796      public static File expandFilename (File name) {
797        String path = name.getPath();
798        String newname = expandFilename (path);
799        @SuppressWarnings("interning")
800        boolean changed = (newname != path);
801        if (changed)
802          return new File (newname);
803        else
804          return name;
805      }
806    
807      /**
808       * Does tilde expansion on a file name (to the user's home directory).
809       */
810      public static String expandFilename (String name) {
811        if (name.contains ("~"))
812          return (name.replace ("~", userHome));
813        else
814          return name;
815      }
816    
817    
818      /**
819       * Returns a string version of the name that can be used in Java source.
820       * On Windows, the file will return a backslash separated string.  Since
821       * backslash is an escape character, it must be quoted itself inside
822       * the string.
823       *
824       * The current implementation presumes that backslashes don't appear
825       * in filenames except as windows path separators.  That seems like a
826       * reasonable assumption.
827       */
828      public static String java_source (File name) {
829    
830        return name.getPath().replace ("\\", "\\\\");
831      }
832    
833      ///
834      /// Reading and writing
835      ///
836    
837      /**
838       * Writes an Object to a File.
839       **/
840      public static void writeObject(Object o, File file) throws IOException {
841        // 8192 is the buffer size in BufferedReader
842        OutputStream bytes =
843          new BufferedOutputStream(new FileOutputStream(file), 8192);
844        if (file.getName().endsWith(".gz")) {
845          bytes = new GZIPOutputStream(bytes);
846        }
847        ObjectOutputStream objs = new ObjectOutputStream(bytes);
848        objs.writeObject(o);
849        objs.close();
850      }
851    
852    
853      /**
854       * Reads an Object from a File.
855       **/
856      public static Object readObject(File file) throws
857      IOException, ClassNotFoundException {
858        // 8192 is the buffer size in BufferedReader
859        InputStream istream =
860          new BufferedInputStream(new FileInputStream(file), 8192);
861        if (file.getName().endsWith(".gz")) {
862          istream = new GZIPInputStream(istream);
863        }
864        ObjectInputStream objs = new ObjectInputStream(istream);
865        return objs.readObject();
866      }
867    
868      /**
869       * Reads the entire contents of the reader and returns it as a string.
870       * Any IOException encountered will be turned into an Error.
871       */
872      public static String readerContents(Reader r) {
873        try {
874          StringBuilder contents = new StringBuilder();
875          int ch;
876          while ((ch = r.read()) != -1) {
877            contents.append((char) ch);
878          }
879          r.close();
880          return contents.toString();
881        } catch (Exception e) {
882          throw new Error ("Unexpected error in readerContents(" + r + ")", e);
883        }
884      }
885    
886      // an alternate name would be "fileContents".
887      /**
888       * Reads the entire contents of the file and returns it as a string.
889       * Any IOException encountered will be turned into an Error.
890       */
891      public static String readFile (File file) {
892    
893        try {
894          BufferedReader reader = UtilMDE.bufferedFileReader (file);
895          StringBuilder contents = new StringBuilder();
896          String line = reader.readLine();
897          while (line != null) {
898            contents.append (line);
899            // Note that this converts line terminators!
900            contents.append (lineSep);
901            line = reader.readLine();
902          }
903          reader.close();
904          return contents.toString();
905        } catch (Exception e) {
906          throw new Error ("Unexpected error in readFile(" + file + ")", e);
907        }
908      }
909    
910      /**
911       * Creates a file with the given name and writes the specified string
912       * to it.  If the file currently exists (and is writable) it is overwritten
913       * Any IOException encountered will be turned into an Error.
914       */
915      public static void writeFile (File file, String contents) {
916    
917        try {
918          FileWriter writer = new FileWriter (file);
919          writer.write (contents, 0, contents.length());
920          writer.close();
921        } catch (Exception e) {
922          throw new Error ("Unexpected error in writeFile(" + file + ")", e);
923        }
924      }
925    
926    
927      ///////////////////////////////////////////////////////////////////////////
928      /// Hashing
929      ///
930    
931      // In hashing, there are two separate issues.  First, one must convert
932      // the input datum into an integer.  Then, one must transform the
933      // resulting integer in a pseudorandom way so as to result in a number
934      // that is far separated from other values that may have been near it to
935      // begin with.  Often these two steps are combined, particularly if
936      // one wishes to avoid creating too large an integer (losing information
937      // off the top bits).
938    
939      // http://burtleburtle.net/bob/hash/hashfaq.html says (of combined methods):
940      //  * for (h=0, i=0; i<len; ++i) { h += key[i]; h += (h<<10); h ^= (h>>6); }
941      //    h += (h<<3); h ^= (h>>11); h += (h<<15);
942      //    is good.
943      //  * for (h=0, i=0; i<len; ++i) h = tab[(h^key[i])&0xff]; may be good.
944      //  * for (h=0, i=0; i<len; ++i) h = (h>>8)^tab[(key[i]+h)&0xff]; may be good.
945    
946      // In this part of the file, perhaps I will eventually write good hash
947      // functions.  For now, write cheesy ones that primarily deal with the
948      // first issue, transforming a data structure into a single number.  This
949      // is also known as fingerprinting.
950    
951      // Note that this differs from the result of Double.hashCode (which see).
952      public static final int hash(double x) {
953        return hash(Double.doubleToLongBits(x));
954      }
955    
956      public static final int hash(double a, double b) {
957        double result = 17;
958        result = result * 37 + a;
959        result = result * 37 + b;
960        return hash(result);
961      }
962    
963      public static final int hash(double a, double b, double c) {
964        double result = 17;
965        result = result * 37 + a;
966        result = result * 37 + b;
967        result = result * 37 + c;
968        return hash(result);
969      }
970    
971      public static final int hash(double /*@Nullable*/ [] a) {
972        double result = 17;
973        if (a != null) {
974          result = result * 37 + a.length;
975          for (int i = 0; i < a.length; i++) {
976            result = result * 37 + a[i];
977          }
978        }
979        return hash(result);
980      }
981    
982      public static final int hash(double /*@Nullable*/ [] a, double /*@Nullable*/ [] b) {
983        return hash(hash(a), hash(b));
984      }
985    
986    
987      // Don't define hash with int args; use the long versions instead.
988    
989      // Note that this differs from the result of Long.hashCode (which see)
990      // But it doesn't map -1 and 0 to the same value.
991      public static final int hash(long l) {
992        // If possible, use the value itself.
993        if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
994          return (int) l;
995        }
996    
997        int result = 17;
998        int hibits = (int) (l >> 32);
999        int lobits = (int) l;
1000        result = result * 37 + hibits;
1001        result = result * 37 + lobits;
1002        return result;
1003      }
1004    
1005      public static final int hash(long a, long b) {
1006        long result = 17;
1007        result = result * 37 + a;
1008        result = result * 37 + b;
1009        return hash(result);
1010      }
1011    
1012      public static final int hash(long a, long b, long c) {
1013        long result = 17;
1014        result = result * 37 + a;
1015        result = result * 37 + b;
1016        result = result * 37 + c;
1017        return hash(result);
1018      }
1019    
1020      public static final int hash(long /*@Nullable*/ [] a) {
1021        long result = 17;
1022        if (a != null) {
1023          result = result * 37 + a.length;
1024          for (int i = 0; i < a.length; i++) {
1025            result = result * 37 + a[i];
1026          }
1027        }
1028        return hash(result);
1029      }
1030    
1031      public static final int hash(long /*@Nullable*/ [] a, long /*@Nullable*/ [] b) {
1032        return hash(hash(a), hash(b));
1033      }
1034    
1035      public static final int hash(/*@Nullable*/ String a) {
1036        return (a == null) ? 0 : a.hashCode();
1037      }
1038    
1039      public static final int hash(/*@Nullable*/ String a, /*@Nullable*/ String b) {
1040        long result = 17;
1041        result = result * 37 + hash(a);
1042        result = result * 37 + hash(b);
1043        return hash(result);
1044      }
1045    
1046      public static final int hash(/*@Nullable*/ String a, /*@Nullable*/ String b, /*@Nullable*/ String c) {
1047        long result = 17;
1048        result = result * 37 + hash(a);
1049        result = result * 37 + hash(b);
1050        result = result * 37 + hash(c);
1051        return hash(result);
1052      }
1053    
1054      public static final int hash(/*@Nullable*/ String /*@Nullable*/ [] a) {
1055        long result = 17;
1056        if (a != null) {
1057          result = result * 37 + a.length;
1058          for (int i = 0; i < a.length; i++) {
1059            result = result * 37 + hash(a[i]);
1060          }
1061        }
1062        return hash(result);
1063      }
1064    
1065    
1066      ///////////////////////////////////////////////////////////////////////////
1067      /// Iterator
1068      ///
1069    
1070      // Making these classes into functions didn't work because I couldn't get
1071      // their arguments into a scope that Java was happy with.
1072    
1073      /** Converts an Enumeration into an Iterator. */
1074      public static final class EnumerationIterator<T> implements Iterator<T> {
1075        Enumeration<T> e;
1076        public EnumerationIterator(Enumeration<T> e) { this.e = e; }
1077        public boolean hasNext() { return e.hasMoreElements(); }
1078        public T next() { return e.nextElement(); }
1079        public void remove() { throw new UnsupportedOperationException(); }
1080      }
1081    
1082      /** Converts an Iterator into an Enumeration. */
1083      public static final class IteratorEnumeration<T> implements Enumeration<T> {
1084        Iterator<T> itor;
1085        public IteratorEnumeration(Iterator<T> itor) { this.itor = itor; }
1086        public boolean hasMoreElements() { return itor.hasNext(); }
1087        public T nextElement() { return itor.next(); }
1088      }
1089    
1090      // This must already be implemented someplace else.  Right??
1091      /**
1092       * An Iterator that returns first the elements returned by its first
1093       * argument, then the elements returned by its second argument.
1094       * Like MergedIterator, but specialized for the case of two arguments.
1095       **/
1096      public static final class MergedIterator2<T> implements Iterator<T> {
1097        Iterator<T> itor1, itor2;
1098        public MergedIterator2(Iterator<T> itor1_, Iterator<T> itor2_) {
1099          this.itor1 = itor1_; this.itor2 = itor2_;
1100        }
1101        public boolean hasNext() {
1102          return (itor1.hasNext() || itor2.hasNext());
1103        }
1104        public T next() {
1105          if (itor1.hasNext())
1106            return itor1.next();
1107          else if (itor2.hasNext())
1108            return itor2.next();
1109          else
1110            throw new NoSuchElementException();
1111        }
1112        public void remove() {
1113          throw new UnsupportedOperationException();
1114        }
1115      }
1116    
1117      // This must already be implemented someplace else.  Right??
1118      /**
1119       * An Iterator that returns the elements in each of its argument
1120       * Iterators, in turn.  The argument is an Iterator of Iterators.
1121       * Like MergedIterator2, but generalized to arbitrary number of iterators.
1122       **/
1123      public static final class MergedIterator<T> implements Iterator<T> {
1124        Iterator<Iterator<T>> itorOfItors;
1125        public MergedIterator(Iterator<Iterator<T>> itorOfItors) { this.itorOfItors = itorOfItors; }
1126    
1127        // an empty iterator to prime the pump
1128        Iterator<T> current = new ArrayList<T>().iterator();
1129    
1130        public boolean hasNext() {
1131          while ((!current.hasNext()) && (itorOfItors.hasNext())) {
1132            current = itorOfItors.next();
1133          }
1134          return current.hasNext();
1135        }
1136    
1137        public T next() {
1138          hasNext();                // for side effect
1139          return current.next();
1140        }
1141    
1142        public void remove() {
1143          throw new UnsupportedOperationException();
1144        }
1145      }
1146    
1147      public static final class FilteredIterator<T> implements Iterator<T> {
1148        Iterator<T> itor;
1149        Filter<T> filter;
1150    
1151        public FilteredIterator(Iterator<T> itor, Filter<T> filter) {
1152          this.itor = itor; this.filter = filter;
1153        }
1154    
1155        @SuppressWarnings("unchecked")
1156        T invalid_t = (T) new Object();
1157    
1158        T current = invalid_t;
1159        boolean current_valid = false;
1160    
1161        public boolean hasNext() {
1162          while ((!current_valid) && itor.hasNext()) {
1163            current = itor.next();
1164            current_valid = filter.accept(current);
1165          }
1166          return current_valid;
1167        }
1168    
1169        public T next() {
1170          if (hasNext()) {
1171            current_valid = false;
1172            @SuppressWarnings("interning")
1173            boolean ok = (current != invalid_t);
1174            assert ok;
1175            return current;
1176          } else {
1177            throw new NoSuchElementException();
1178          }
1179        }
1180    
1181        public void remove() {
1182          throw new UnsupportedOperationException();
1183        }
1184      }
1185    
1186      /**
1187       * Returns an iterator just like its argument, except that the first and
1188       * last elements are removed.  They can be accessed via the getFirst and
1189       * getLast methods.
1190       **/
1191      public static final class RemoveFirstAndLastIterator<T> implements Iterator<T> {
1192        Iterator<T> itor;
1193        // I don't think this works, because the iterator might itself return null
1194        // /*@Nullable*/ T nothing = (/*@Nullable*/ T) null;
1195        @SuppressWarnings("unchecked")
1196        T nothing = (T) new Object();
1197        T first = nothing;
1198        T current = nothing;
1199    
1200        public RemoveFirstAndLastIterator(Iterator<T> itor) {
1201          this.itor = itor;
1202          if (itor.hasNext()) {
1203            first = itor.next();
1204          }
1205          if (itor.hasNext()) {
1206            current = itor.next();
1207          }
1208        }
1209    
1210        public boolean hasNext() {
1211          return itor.hasNext();
1212        }
1213    
1214        public T next() {
1215          if (! itor.hasNext()) {
1216            throw new NoSuchElementException();
1217          }
1218          T tmp = current;
1219          current = itor.next();
1220          return tmp;
1221        }
1222    
1223        public T getFirst() {
1224          @SuppressWarnings("interning")
1225          boolean invalid = (first == nothing);
1226          if (invalid) {
1227            throw new NoSuchElementException();
1228          }
1229          return first;
1230        }
1231    
1232        // Throws an error unless the RemoveFirstAndLastIterator has already
1233        // been iterated all the way to its end (so the delegate is pointing to
1234        // the last element).  Also, this is buggy when the delegate is empty.
1235        public T getLast() {
1236          if (itor.hasNext()) {
1237            throw new Error();
1238          }
1239          return current;
1240        }
1241    
1242        public void remove() {
1243          throw new UnsupportedOperationException();
1244        }
1245      }
1246    
1247    
1248      /**
1249       * Return an List containing num_elts randomly chosen
1250       * elements from the iterator, or all the elements of the iterator if
1251       * there are fewer.  It examines every element of the iterator, but does
1252       * not keep them all in memory.
1253       **/
1254      public static <T> List<T> randomElements(Iterator<T> itor, int num_elts) {
1255        return randomElements(itor, num_elts, r);
1256      }
1257      private static Random r = new Random();
1258    
1259      /**
1260       * Return an List containing num_elts randomly chosen
1261       * elements from the iterator, or all the elements of the iterator if
1262       * there are fewer.  It examines every element of the iterator, but does
1263       * not keep them all in memory.
1264       **/
1265      public static <T> List<T> randomElements(Iterator<T> itor, int num_elts, Random random) {
1266        // The elements are chosen with the following probabilities,
1267        // where n == num_elts:
1268        //   n n/2 n/3 n/4 n/5 ...
1269    
1270        RandomSelector<T> rs = new RandomSelector<T> (num_elts, random);
1271    
1272        while (itor.hasNext()) {
1273          rs.accept (itor.next());
1274        }
1275        return rs.getValues();
1276    
1277    
1278        /*
1279        ArrayList<T> result = new ArrayList<T>(num_elts);
1280        int i=1;
1281        for (int n=0; n<num_elts && itor.hasNext(); n++, i++) {
1282          result.add(itor.next());
1283        }
1284        for (; itor.hasNext(); i++) {
1285          T o = itor.next();
1286          // test random < num_elts/i
1287          if (random.nextDouble() * i < num_elts) {
1288            // This element will replace one of the existing elements.
1289            result.set(random.nextInt(num_elts), o);
1290          }
1291        }
1292        return result;
1293        */
1294      }
1295    
1296    
1297      ///////////////////////////////////////////////////////////////////////////
1298      /// Map
1299      ///
1300    
1301      // In Python, inlining this gave a 10x speed improvement.
1302      // Will the same be true for Java?
1303      /**
1304       * Increment the Integer which is indexed by key in the Map.
1305       * If the key isn't in the Map, it is added.
1306       * Throws an error if the key is in the Map but maps to a non-Integer.
1307       **/
1308      public static <T> /*@Nullable*/ Integer incrementMap(Map<T,Integer> m, T key, int count) {
1309        Integer old = m.get(key);
1310        int new_total;
1311        if (old == null) {
1312          new_total = count;
1313        } else {
1314          new_total = old.intValue() + count;
1315        }
1316        return m.put(key, new Integer(new_total));
1317      }
1318    
1319      public static <K,V> String mapToString(Map<K,V> m) {
1320        StringBuilder sb = new StringBuilder();
1321        mapToString(sb, m, "");
1322        return sb.toString();
1323      }
1324    
1325      /**
1326       * Write a multi-line representation of the map into the given Appendable
1327       * (e.g., a StringBuilder).
1328       */
1329      public static <K,V> void mapToString(Appendable sb, Map<K,V> m, String linePrefix) {
1330        try {
1331          for (Map.Entry<K, V> entry : m.entrySet()) {
1332            sb.append(linePrefix);
1333            sb.append(entry.getKey().toString());
1334            sb.append(" => ");
1335            sb.append(entry.getValue().toString());
1336            sb.append(lineSep);
1337          }
1338        } catch (IOException e) {
1339          throw new RuntimeException(e);
1340        }
1341      }
1342    
1343      /** Returns a sorted version of m.keySet(). */
1344      public static <K extends Comparable<? super K>,V> Collection</*@KeyFor("#0")*/ K> sortedKeySet(Map<K,V> m) {
1345        ArrayList<K> theKeys = new ArrayList<K> (m.keySet());
1346        Collections.sort (theKeys);
1347        return theKeys;
1348      }
1349    
1350      /** Returns a sorted version of m.keySet(). */
1351      public static <K,V> Collection</*@KeyFor("#0")*/ K> sortedKeySet(Map<K,V> m, Comparator<K> comparator) {
1352        ArrayList<K> theKeys = new ArrayList<K> (m.keySet());
1353        Collections.sort (theKeys, comparator);
1354        return theKeys;
1355      }
1356    
1357    
1358      ///////////////////////////////////////////////////////////////////////////
1359      /// Method
1360      ///
1361    
1362      // maps from a string of arg names to an array of Class objects.
1363      static HashMap<String,Class<?>[]> args_seen = new HashMap<String,Class<?>[]>();
1364    
1365      public static Method methodForName(String method)
1366        throws ClassNotFoundException, NoSuchMethodException, SecurityException {
1367    
1368        int oparenpos = method.indexOf('(');
1369        int dotpos = method.lastIndexOf('.', oparenpos);
1370        int cparenpos = method.indexOf(')', oparenpos);
1371        if ((dotpos == -1) || (oparenpos == -1) || (cparenpos == -1)) {
1372          throw new Error("malformed method name should contain a period, open paren, and close paren: " + method + " <<" + dotpos + "," + oparenpos + "," + cparenpos + ">>");
1373        }
1374        for (int i=cparenpos+1; i<method.length(); i++) {
1375          if (! Character.isWhitespace(method.charAt(i))) {
1376            throw new Error("malformed method name should contain only whitespace following close paren");
1377          }
1378        }
1379    
1380        String classname = method.substring(0,dotpos);
1381        String methodname = method.substring(dotpos+1, oparenpos);
1382        String all_argnames = method.substring(oparenpos+1, cparenpos).trim();
1383        Class<?>[] argclasses = args_seen.get(all_argnames);
1384        if (argclasses == null) {
1385          String[] argnames;
1386          if (all_argnames.equals("")) {
1387            argnames = new String[0];
1388          } else {
1389            argnames = split(all_argnames, ',');
1390          }
1391    
1392          argclasses = new Class<?>[argnames.length];
1393          for (int i=0; i<argnames.length; i++) {
1394            String argname = argnames[i].trim();
1395            int numbrackets = 0;
1396            while (argname.endsWith("[]")) {
1397              argname = argname.substring(0, argname.length()-2);
1398              numbrackets++;
1399            }
1400            if (numbrackets > 0) {
1401              argname = "L" + argname + ";";
1402              while (numbrackets>0) {
1403                argname = "[" + argname;
1404                numbrackets--;
1405              }
1406            }
1407            // System.out.println("argname " + i + " = " + argname + " for method " + method);
1408            argclasses[i] = classForName(argname);
1409          }
1410          args_seen.put(all_argnames, argclasses);
1411        }
1412        return methodForName(classname, methodname, argclasses);
1413      }
1414    
1415      public static Method methodForName(String classname, String methodname, Class<?>[] params)
1416        throws ClassNotFoundException, NoSuchMethodException, SecurityException {
1417    
1418        Class<?> c = Class.forName(classname);
1419        Method m = c.getDeclaredMethod(methodname, params);
1420        return m;
1421      }
1422    
1423    
1424    
1425      ///////////////////////////////////////////////////////////////////////////
1426      /// ProcessBuilder
1427      ///
1428    
1429      /**
1430       * Execute the given command, and return all its output as a string.
1431       */
1432      public static String backticks(String... command) {
1433        return backticks(Arrays.asList(command));
1434      }
1435    
1436      /**
1437       * Execute the given command, and return all its output as a string.
1438       */
1439      public static String backticks(List<String> command) {
1440        ProcessBuilder pb = new ProcessBuilder(command);
1441        pb.redirectErrorStream(true);
1442        // TimeLimitProcess p = new TimeLimitProcess(pb.start(), TIMEOUT_SEC * 1000);
1443        try {
1444          Process p = pb.start();
1445          String output = UtilMDE.streamString(p.getInputStream());
1446          return output;
1447        } catch (IOException e) {
1448          return "IOException: " + e.getMessage();
1449        }
1450      }
1451    
1452    
1453      ///////////////////////////////////////////////////////////////////////////
1454      /// Properties
1455      ///
1456    
1457      /**
1458       * Determines whether a property has value "true", "yes", or "1".
1459       * @see Properties#getProperty
1460       **/
1461      public static boolean propertyIsTrue(Properties p, String key) {
1462        String pvalue = p.getProperty(key);
1463        if (pvalue == null) {
1464          return false;
1465        }
1466        pvalue = pvalue.toLowerCase();
1467        return (pvalue.equals("true") || pvalue.equals("yes") || pvalue.equals("1"));
1468      }
1469    
1470      /**
1471       * Set the property to its previous value concatenated to the given value.
1472       * Returns the previous value.
1473       * @see Properties#getProperty
1474       * @see Properties#setProperty
1475       **/
1476      public static /*@Nullable*/ String appendProperty(Properties p, String key, String value) {
1477        return (String)p.setProperty(key, p.getProperty(key, "") + value);
1478      }
1479    
1480      /**
1481       * Set the property only if it was not previously set.
1482       * @deprecated use setDefaultMaybe
1483       * @see Properties#getProperty
1484       * @see Properties#setProperty
1485       **/
1486      @Deprecated
1487      public static /*@Nullable*/ String setDefault(Properties p, String key, String value) {
1488        String currentValue = p.getProperty(key);
1489        if (currentValue == null) {
1490          p.setProperty(key, value);
1491        }
1492        return currentValue;
1493      }
1494    
1495      /**
1496       * Set the property only if it was not previously set.
1497       * @see Properties#getProperty
1498       * @see Properties#setProperty
1499       **/
1500      public static /*@Nullable*/ String setDefaultMaybe(Properties p, String key, String value) {
1501        String currentValue = p.getProperty(key);
1502        if (currentValue == null) {
1503          p.setProperty(key, value);
1504        }
1505        return currentValue;
1506      }
1507    
1508    
1509      ///////////////////////////////////////////////////////////////////////////
1510      /// Regexp (regular expression)
1511      ///
1512    
1513        // Stolen from JDK 1.5.  Intended for use (and viewing) only by JRL
1514        // licensees (if you are not one, proceed no further).  To be removed
1515        // as soon as we migrate to Java 1.5.
1516        /**
1517         * Returns a literal pattern <code>String</code> for the specified
1518         * <code>String</code>.
1519         *
1520         * <p>This method produces a <code>String</code> that can be used to
1521         * create a <code>Pattern</code> that would match the string
1522         * <code>s</code> as if it were a literal pattern.</p> Metacharacters
1523         * or escape sequences in the input sequence will be given no special
1524         * meaning.
1525         *
1526         * @param  s The string to be literalized
1527         * @return  A literal string replacement
1528         * @since 1.5
1529         */
1530        public static String patternQuote(String s) {
1531            int slashEIndex = s.indexOf("\\E");
1532            if (slashEIndex == -1)
1533                return "\\Q" + s + "\\E";
1534    
1535            StringBuffer sb = new StringBuffer(s.length() * 2);
1536            sb.append("\\Q");
1537            slashEIndex = 0;
1538            int current = 0;
1539            while ((slashEIndex = s.indexOf("\\E", current)) != -1) {
1540                sb.append(s.substring(current, slashEIndex));
1541                current = slashEIndex + 2;
1542                sb.append("\\E\\\\E\\Q");
1543            }
1544            sb.append(s.substring(current, s.length()));
1545            sb.append("\\E");
1546            return sb.toString();
1547        }
1548    
1549    
1550      ///////////////////////////////////////////////////////////////////////////
1551      /// Reflection
1552      ///
1553    
1554      /**
1555       * Sets the given field, which may be final.
1556       * Intended for use in readObject and nowhere else!
1557       */
1558      public static void setFinalField(Object o, String fieldName, Object value)
1559        throws NoSuchFieldException, IllegalAccessException {
1560        Class<?> c = o.getClass();
1561        while (c != Object.class) { // Class is interned
1562          // System.out.printf ("Setting field %s in %s%n", fieldName, c);
1563          try {
1564            Field f = c.getDeclaredField(fieldName);
1565            f.setAccessible(true);
1566            f.set(o, value);
1567            return;
1568          } catch (NoSuchFieldException e) {
1569            if (c.getSuperclass() == Object.class) // Class is interned
1570              throw e;
1571          }
1572          c = c.getSuperclass();
1573          assert c != null : "@SuppressWarnings(nullness): c was not Object, so is not null now";
1574        }
1575        throw new NoSuchFieldException (fieldName);
1576      }
1577    
1578    
1579      ///////////////////////////////////////////////////////////////////////////
1580      /// Set
1581      ///
1582    
1583      /**
1584       * Returns the object in this set that is equal to key.
1585       * The Set abstraction doesn't provide this; it only provides "contains".
1586       * Returns null if the argument is null, or if it isn't in the set.
1587       **/
1588      public static /*@Nullable*/ Object getFromSet(Set<?> set, Object key) {
1589        if (key == null) {
1590          return null;
1591        }
1592        for (Object elt : set) {
1593          if (key.equals(elt)) {
1594            return elt;
1595          }
1596        }
1597        return null;
1598      }
1599    
1600    
1601      ///////////////////////////////////////////////////////////////////////////
1602      /// Stream
1603      ///
1604    
1605      /** Copy the contents of the input stream to the output stream. */
1606      public static void streamCopy(java.io.InputStream from, java.io.OutputStream to) {
1607        byte[] buffer = new byte[1024];
1608        int bytes;
1609        try {
1610          while (true) {
1611            bytes = from.read(buffer);
1612            if (bytes == -1) {
1613              return;
1614            }
1615            to.write(buffer, 0, bytes);
1616          }
1617        } catch (java.io.IOException e) {
1618          e.printStackTrace();
1619          throw new Error(e);
1620        }
1621      }
1622    
1623      /** Return a String containing all the characters from the input stream. **/
1624      public static String streamString(java.io.InputStream is) {
1625        ByteArrayOutputStream baos = new ByteArrayOutputStream();
1626        streamCopy(is, baos);
1627        return baos.toString();
1628      }
1629    
1630    
1631      ///////////////////////////////////////////////////////////////////////////
1632      /// String
1633      ///
1634    
1635      /**
1636       * Return a new string which is the text of target with all instances of
1637       * oldStr replaced by newStr.
1638       **/
1639      public static String replaceString(String target, String oldStr, String newStr) {
1640        if (oldStr.equals("")) throw new IllegalArgumentException();
1641    
1642        StringBuffer result = new StringBuffer();
1643        int lastend = 0;
1644        int pos;
1645        while ((pos = target.indexOf(oldStr, lastend)) != -1) {
1646          result.append(target.substring(lastend, pos));
1647          result.append(newStr);
1648          lastend = pos + oldStr.length();
1649        }
1650        result.append(target.substring(lastend));
1651        return result.toString();
1652      }
1653    
1654      /**
1655       * Return an array of Strings representing the characters between
1656       * successive instances of the delimiter character.
1657       * Always returns an array of length at least 1 (it might contain only the
1658       * empty string).
1659       * @see #split(String s, String delim)
1660       **/
1661      public static String[] split(String s, char delim) {
1662        Vector<String> result = new Vector<String>();
1663        for (int delimpos = s.indexOf(delim); delimpos != -1; delimpos = s.indexOf(delim)) {
1664          result.add(s.substring(0, delimpos));
1665          s = s.substring(delimpos+1);
1666        }
1667        result.add(s);
1668        String[] result_array = new String[result.size()];
1669        result.copyInto(result_array);
1670        return result_array;
1671      }
1672    
1673      /**
1674       * Return an array of Strings representing the characters between
1675       * successive instances of the delimiter String.
1676       * Always returns an array of length at least 1 (it might contain only the
1677       * empty string).
1678       * @see #split(String s, char delim)
1679       **/
1680      public static String[] split(String s, String delim) {
1681        int delimlen = delim.length();
1682        if (delimlen == 0) {
1683          throw new Error("Second argument to split was empty.");
1684        }
1685        Vector<String> result = new Vector<String>();
1686        for (int delimpos = s.indexOf(delim); delimpos != -1; delimpos = s.indexOf(delim)) {
1687          result.add(s.substring(0, delimpos));
1688          s = s.substring(delimpos+delimlen);
1689        }
1690        result.add(s);
1691        String[] result_array = new String[result.size()];
1692        result.copyInto(result_array);
1693        return result_array;
1694      }
1695    
1696      /**
1697       * Return an array of Strings, one for each line in the argument.
1698       * Always returns an array of length at least 1 (it might contain only the
1699       * empty string).  All common line separators (cr, lf, cr-lf, or lf-cr)
1700       * are supported.  Note that a string that ends with a line separator
1701       * will return an empty string as the last element of the array.
1702       * @see #split(String s, char delim)
1703       **/
1704      public static String[] splitLines(String s) {
1705        return s.split ("\r\n?|\n\r?", -1);
1706      }
1707    
1708      /**
1709       * Concatenate the string representations of the objects, placing the
1710       * delimiter between them.
1711       * @see plume.ArraysMDE#toString(int[])
1712       **/
1713      public static String join(Object[] a, String delim) {
1714        if (a.length == 0) return "";
1715        if (a.length == 1) return String.valueOf(a[0]);
1716        StringBuffer sb = new StringBuffer(String.valueOf(a[0]));
1717        for (int i=1; i<a.length; i++)
1718          sb.append(delim).append(a[i]);
1719        return sb.toString();
1720      }
1721    
1722      /**
1723       * Concatenate the string representations of the objects, placing the
1724       * system-specific line separator between them.
1725       * @see plume.ArraysMDE#toString(int[])
1726       **/
1727      public static String joinLines(Object... a) {
1728        return join(a, lineSep);
1729      }
1730    
1731      /**
1732       * Concatenate the string representations of the objects, placing the
1733       * delimiter between them.
1734       * @see java.util.AbstractCollection#toString()
1735       **/
1736      public static String join(List<?> v, String delim) {
1737        if (v.size() == 0) return "";
1738        if (v.size() == 1) return v.get(0).toString();
1739        // This should perhaps use an iterator rather than get().
1740        StringBuffer sb = new StringBuffer(v.get(0).toString());
1741        for (int i=1; i<v.size(); i++)
1742          sb.append(delim).append(v.get(i));
1743        return sb.toString();
1744      }
1745    
1746      /**
1747       * Concatenate the string representations of the objects, placing the
1748       * system-specific line separator between them.
1749       * @see java.util.AbstractCollection#toString()
1750       **/
1751      public static String joinLines(List<String> v, String delim) {
1752        return join(v, lineSep);
1753      }
1754    
1755      // Inspired by the 'quote' function in Ajax (but independent code).
1756      /**
1757       * Escape \, ", newline, and carriage-return characters in the
1758       * target as \\, \\", \n, and \r; return a new string if any
1759       * modifications were necessary. The intent is that by surrounding
1760       * the return value with double quote marks, the result will be a
1761       * Java string literal denoting the original string. Previously
1762       * known as quote().
1763       **/
1764      public static String escapeNonJava(String orig) {
1765        StringBuffer sb = new StringBuffer();
1766        // The previous escape character was seen right before this position.
1767        int post_esc = 0;
1768        int orig_len = orig.length();
1769        for (int i=0; i<orig_len; i++) {
1770          char c = orig.charAt(i);
1771          switch (c) {
1772          case '\"':
1773          case '\\':
1774            if (post_esc < i) {
1775              sb.append(orig.substring(post_esc, i));
1776            }
1777            sb.append('\\');
1778            post_esc = i;
1779            break;
1780          case '\n':                // not lineSep
1781            if (post_esc < i) {
1782              sb.append(orig.substring(post_esc, i));
1783            }
1784            sb.append("\\n");       // not lineSep
1785            post_esc = i+1;
1786            break;
1787          case '\r':
1788            if (post_esc < i) {
1789              sb.append(orig.substring(post_esc, i));
1790            }
1791            sb.append("\\r");
1792            post_esc = i+1;
1793            break;
1794          default:
1795            // Nothing to do: i gets incremented
1796          }
1797        }
1798        if (sb.length() == 0)
1799          return orig;
1800        sb.append(orig.substring(post_esc));
1801        return sb.toString();
1802      }
1803    
1804      // The overhead of this is too high to call in escapeNonJava(String)
1805      public static String escapeNonJava(Character ch) {
1806        char c = ch.charValue();
1807        switch (c) {
1808        case '\"':
1809          return "\\\"";
1810        case '\\':
1811          return "\\\\";
1812        case '\n':                  // not lineSep
1813          return "\\n";             // not lineSep
1814        case '\r':
1815          return "\\r";
1816        default:
1817          return new String(new char[] { c });
1818        }
1819      }
1820    
1821      /**
1822       * Escape unprintable characters in the target, following the usual
1823       * Java backslash conventions, so that the result is sure to be
1824       * printable ASCII.  Returns a new string.
1825       **/
1826      public static String escapeNonASCII(String orig) {
1827        StringBuffer sb = new StringBuffer();
1828        int orig_len = orig.length();
1829        for (int i=0; i<orig_len; i++) {
1830          char c = orig.charAt(i);
1831          sb.append(escapeNonASCII(c));
1832        }
1833        return sb.toString();
1834      }
1835    
1836      /**
1837       * Like escapeNonJava(), but quote more characters so that the
1838       * result is sure to be printable ASCII. Not particularly optimized.
1839       **/
1840      private static String escapeNonASCII(char c) {
1841        if (c == '"') {
1842          return "\\\"";
1843        } else if (c == '\\') {
1844          return "\\\\";
1845        } else if (c == '\n') {     // not lineSep
1846          return "\\n";             // not lineSep
1847        } else if (c == '\r') {
1848          return "\\r";
1849        } else if (c == '\t') {
1850          return "\\t";
1851        } else if (c >= ' ' && c <= '~') {
1852          return new String(new char[] { c });
1853        } else if (c < 256) {
1854          String octal = Integer.toOctalString(c);
1855          while (octal.length() < 3)
1856            octal = '0' + octal;
1857          return "\\" + octal;
1858        } else {
1859          String hex = Integer.toHexString(c);
1860          while (hex.length() < 4)
1861            hex = "0" + hex;
1862          return "\\u" + hex;
1863        }
1864      }
1865    
1866      /**
1867       * Replace "\\", "\"", "\n", and "\r" sequences by their
1868       * one-character equivalents.  All other backslashes are removed
1869       * (for instance, octal/hex escape sequences are not turned into
1870       * their respective characters). This is the inverse operation of
1871       * escapeNonJava(). Previously known as unquote().
1872       **/
1873      public static String unescapeNonJava(String orig) {
1874        StringBuffer sb = new StringBuffer();
1875        // The previous escape character was seen just before this position.
1876        int post_esc = 0;
1877        int this_esc = orig.indexOf('\\');
1878        while (this_esc != -1) {
1879          if (this_esc == orig.length()-1) {
1880            sb.append(orig.substring(post_esc, this_esc+1));
1881            post_esc = this_esc+1;
1882            break;
1883          }
1884          switch (orig.charAt(this_esc+1)) {
1885          case 'n':
1886            sb.append(orig.substring(post_esc, this_esc));
1887            sb.append('\n');        // not lineSep
1888            post_esc = this_esc+2;
1889            break;
1890          case 'r':
1891            sb.append(orig.substring(post_esc, this_esc));
1892            sb.append('\r');
1893            post_esc = this_esc+2;
1894            break;
1895          case '\\':
1896            // This is not in the default case because the search would find
1897            // the quoted backslash.  Here we incluce the first backslash in
1898            // the output, but not the first.
1899            sb.append(orig.substring(post_esc, this_esc+1));
1900            post_esc = this_esc+2;
1901            break;
1902    
1903          case '0': case '1': case '2': case '3': case '4':
1904          case '5': case '6': case '7': case '8': case '9':
1905            sb.append(orig.substring(post_esc, this_esc));
1906            char octal_char = 0;
1907            int ii = this_esc+1;
1908            while (ii < orig.length()) {
1909              char ch = orig.charAt(ii++);
1910              if ((ch < '0') || (ch > '8'))
1911                break;
1912              octal_char = (char) ((octal_char * 8)+ Character.digit (ch, 8));
1913            }
1914            sb.append (octal_char);
1915            post_esc = ii-1;
1916            break;
1917    
1918          default:
1919            // In the default case, retain the character following the backslash,
1920            // but discard the backslash itself.  "\*" is just a one-character string.
1921            sb.append(orig.substring(post_esc, this_esc));
1922            post_esc = this_esc+1;
1923            break;
1924          }
1925          this_esc = orig.indexOf('\\', post_esc);
1926        }
1927        if (post_esc == 0)
1928          return orig;
1929        sb.append(orig.substring(post_esc));
1930        return sb.toString();
1931      }
1932    
1933      // Use the built-in String.trim()!
1934      // /** Return the string with all leading and trailing whitespace stripped. */
1935      // public static String trimWhitespace(String s) {
1936      //   int len = s.length();
1937      //   if (len == 0)
1938      //     return s;
1939      //   int first_non_ws = 0;
1940      //   int last_non_ws = len-1;
1941      //   while ((first_non_ws < len) && Character.isWhitespace(s.charAt(first_non_ws)))
1942      //     first_non_ws++;
1943      //   if (first_non_ws == len)
1944      //     return "";
1945      //   while (Character.isWhitespace(s.charAt(last_non_ws)))
1946      //     last_non_ws--;
1947      //   if ((first_non_ws == 0) && (last_non_ws == len))
1948      //     return s;
1949      //   else
1950      //     return s.substring(first_non_ws, last_non_ws+1);
1951      // }
1952      // // // Testing:
1953      // // assert(UtilMDE.trimWhitespace("foo").equals("foo"));
1954      // // assert(UtilMDE.trimWhitespace(" foo").equals("foo"));
1955      // // assert(UtilMDE.trimWhitespace("    foo").equals("foo"));
1956      // // assert(UtilMDE.trimWhitespace("foo ").equals("foo"));
1957      // // assert(UtilMDE.trimWhitespace("foo    ").equals("foo"));
1958      // // assert(UtilMDE.trimWhitespace("  foo   ").equals("foo"));
1959      // // assert(UtilMDE.trimWhitespace("  foo  bar   ").equals("foo  bar"));
1960      // // assert(UtilMDE.trimWhitespace("").equals(""));
1961      // // assert(UtilMDE.trimWhitespace("   ").equals(""));
1962    
1963    
1964      /** Remove all whitespace before or after instances of delimiter. **/
1965      public static String removeWhitespaceAround(String arg, String delimiter) {
1966        arg = removeWhitespaceBefore(arg, delimiter);
1967        arg = removeWhitespaceAfter(arg, delimiter);
1968        return arg;
1969      }
1970    
1971      /** Remove all whitespace after instances of delimiter. **/
1972      public static String removeWhitespaceAfter(String arg, String delimiter) {
1973        // String orig = arg;
1974        int delim_len = delimiter.length();
1975        int delim_index = arg.indexOf(delimiter);
1976        while (delim_index > -1) {
1977          int non_ws_index = delim_index+delim_len;
1978          while ((non_ws_index < arg.length())
1979                 && (Character.isWhitespace(arg.charAt(non_ws_index)))) {
1980            non_ws_index++;
1981          }
1982          // if (non_ws_index == arg.length()) {
1983          //   System.out.println("No nonspace character at end of: " + arg);
1984          // } else {
1985          //   System.out.println("'" + arg.charAt(non_ws_index) + "' not a space character at " + non_ws_index + " in: " + arg);
1986          // }
1987          if (non_ws_index != delim_index+delim_len) {
1988            arg = arg.substring(0, delim_index + delim_len) + arg.substring(non_ws_index);
1989          }
1990          delim_index = arg.indexOf(delimiter, delim_index+1);
1991        }
1992        return arg;
1993      }
1994    
1995      /** Remove all whitespace before instances of delimiter. **/
1996      public static String removeWhitespaceBefore(String arg, String delimiter) {
1997        // System.out.println("removeWhitespaceBefore(\"" + arg + "\", \"" + delimiter + "\")");
1998        // String orig = arg;
1999        int delim_len = delimiter.length();
2000        int delim_index = arg.indexOf(delimiter);
2001        while (delim_index > -1) {
2002          int non_ws_index = delim_index-1;
2003          while ((non_ws_index >= 0)
2004                 && (Character.isWhitespace(arg.charAt(non_ws_index)))) {
2005            non_ws_index--;
2006          }
2007          // if (non_ws_index == -1) {
2008          //   System.out.println("No nonspace character at front of: " + arg);
2009          // } else {
2010          //   System.out.println("'" + arg.charAt(non_ws_index) + "' not a space character at " + non_ws_index + " in: " + arg);
2011          // }
2012          if (non_ws_index != delim_index-1) {
2013            arg = arg.substring(0, non_ws_index + 1) + arg.substring(delim_index);
2014          }
2015          delim_index = arg.indexOf(delimiter, non_ws_index+2);
2016        }
2017        return arg;
2018      }
2019    
2020    
2021      // @return either "n noun" or "n nouns" depending on n
2022      public static String nplural(int n, String noun) {
2023        if (n == 1)
2024          return n + " " + noun;
2025        else if (noun.endsWith("s") || noun.endsWith("x") ||
2026                 noun.endsWith("ch") || noun.endsWith("sh"))
2027          return n + " " + noun + "es";
2028        else
2029          return n + " " + noun + "s";
2030      }
2031    
2032    
2033      // Returns a string of the specified length, truncated if necessary,
2034      // and padded with spaces to the left if necessary.
2035      public static String lpad(String s, int length) {
2036        if (s.length() < length) {
2037          StringBuffer buf = new StringBuffer();
2038          for (int i = s.length(); i < length; i++) {
2039            buf.append(' ');
2040          }
2041          return buf.toString() + s;
2042        } else {
2043          return s.substring(0, length);
2044        }
2045      }
2046    
2047      // Returns a string of the specified length, truncated if necessary,
2048      // and padded with spaces to the right if necessary.
2049      public static String rpad(String s, int length) {
2050        if (s.length() < length) {
2051          StringBuffer buf = new StringBuffer(s);
2052          for (int i = s.length(); i < length; i++) {
2053            buf.append(' ');
2054          }
2055          return buf.toString();
2056        } else {
2057          return s.substring(0, length);
2058        }
2059      }
2060    
2061      // Converts the int to a String, then formats it using rpad
2062      public static String rpad(int num, int length) {
2063        return rpad(String.valueOf(num), length);
2064      }
2065    
2066      // Converts the double to a String, then formats it using rpad
2067      public static String rpad(double num, int length) {
2068        return rpad(String.valueOf(num), length);
2069      }
2070    
2071      // Same as built-in String comparison, but accept null arguments,
2072      // and place them at the beginning.
2073      public static class NullableStringComparator
2074        implements Comparator<String>
2075      {
2076        public int compare(String s1, String s2) {
2077          if (s1 == null && s2 == null) return 0;
2078          if (s1 == null && s2 != null) return 1;
2079          if (s1 != null && s2 == null) return -1;
2080          return s1.compareTo(s2);
2081        }
2082      }
2083    
2084      /** Return the number of times the character appears in the string. **/
2085      public static int count(String s, int ch) {
2086        int result = 0;
2087        int pos = s.indexOf(ch);
2088        while (pos > -1) {
2089          result++;
2090          pos = s.indexOf(ch, pos+1);
2091        }
2092        return result;
2093      }
2094    
2095      /** Return the number of times the second string appears in the first. **/
2096      public static int count(String s, String sub) {
2097        int result = 0;
2098        int pos = s.indexOf(sub);
2099        while (pos > -1) {
2100          result++;
2101          pos = s.indexOf(sub, pos+1);
2102        }
2103        return result;
2104      }
2105    
2106    
2107      ///////////////////////////////////////////////////////////////////////////
2108      /// StringTokenizer
2109      ///
2110    
2111      /**
2112       * Return a Vector of the Strings returned by
2113       * {@link java.util.StringTokenizer#StringTokenizer(String,String,boolean)} with the given arguments.
2114       * <p>
2115       * The static type is Vector<Object> because StringTokenizer extends
2116       * Enumeration<Object> instead of Enumeration<String> as it should
2117       * (probably due to backward-compatibility).
2118       **/
2119      public static Vector<Object> tokens(String str, String delim, boolean returnTokens) {
2120        return makeVector(new StringTokenizer(str, delim, returnTokens));
2121      }
2122    
2123      /**
2124       * Return a Vector of the Strings returned by
2125       * {@link java.util.StringTokenizer#StringTokenizer(String,String)} with the given arguments.
2126       **/
2127      public static Vector<Object> tokens(String str, String delim) {
2128        return makeVector(new StringTokenizer(str, delim));
2129      }
2130    
2131      /**
2132       * Return a Vector of the Strings returned by
2133       * {@link java.util.StringTokenizer#StringTokenizer(String)} with the given arguments.
2134       **/
2135      public static Vector<Object> tokens(String str) {
2136        return makeVector(new StringTokenizer(str));
2137      }
2138    
2139    
2140    
2141      ///////////////////////////////////////////////////////////////////////////
2142      /// Throwable
2143      ///
2144    
2145      /** For the current backtrace, do "backtrace(new Throwable())". **/
2146      public static String backTrace(Throwable t) {
2147        StringWriter sw = new StringWriter();
2148        PrintWriter pw = new PrintWriter(sw);
2149        t.printStackTrace(pw);
2150        pw.close();
2151        String result = sw.toString();
2152        return result;
2153      }
2154    
2155      // Deprecated as of 2/1/2004.
2156      /**
2157       * @deprecated use "backtrace(new Throwable())" instead, to avoid
2158       * spurious "at plume.UtilMDE.backTrace(UtilMDE.java:1491)" in output.
2159       * @see #backTrace(Throwable)
2160       **/
2161      @Deprecated
2162      public static String backTrace() {
2163        StringWriter sw = new StringWriter();
2164        PrintWriter pw = new PrintWriter(sw);
2165        new Throwable().printStackTrace(pw);
2166        pw.close();
2167        String result = sw.toString();
2168        // TODO: should remove "at plume.UtilMDE.backTrace(UtilMDE.java:1491)"
2169        return result;
2170      }
2171    
2172    
2173      ///////////////////////////////////////////////////////////////////////////
2174      /// Collections
2175      ///
2176    
2177      /**
2178       * Returns the sorted version of the list.  Does not alter the list.
2179       * Simply calls Collections.sort(List<T>, Comparator<? super T>).
2180       **/
2181      public static <T> List<T> sortList (List<T> l, Comparator<? super T> c) {
2182        List<T> result = new ArrayList<T>(l);
2183        Collections.sort(result, c);
2184        return result;
2185      }
2186    
2187    
2188      /**
2189       * Returns a copy of the list with duplicates removed.
2190       * Retains the original order.
2191       **/
2192      public static <T> List<T> removeDuplicates(List<T> l) {
2193        // There are shorter solutions that do not maintain order.
2194        HashSet<T> hs = new HashSet<T>(l.size());
2195        List<T> result = new ArrayList<T>();
2196        for (T t : l) {
2197          if (hs.add(t)) {
2198            result.add(t);
2199          }
2200        }
2201        return result;
2202      }
2203    
2204    
2205      ///////////////////////////////////////////////////////////////////////////
2206      /// Vector
2207      ///
2208    
2209      /** Returns a vector containing the elements of the enumeration. */
2210      public static <T> Vector<T> makeVector(Enumeration<T> e) {
2211        Vector<T> result = new Vector<T>();
2212        while (e.hasMoreElements()) {
2213          result.addElement(e.nextElement());
2214        }
2215        return result;
2216      }
2217    
2218      // Rather than writing something like VectorToStringArray, use
2219      //   v.toArray(new String[0])
2220    
2221    
2222      /**
2223       * Returns a list of lists of each combination (with repetition, but
2224       * not permutations) of the specified objects starting at index
2225       * start over dims dimensions, for dims &gt; 0.
2226       *
2227       * For example, create_combinations (1, 0, {a, b, c}) returns:
2228       *    {a}, {b}, {c}
2229       *
2230       * And create_combinations (2, 0, {a, b, c}) returns:
2231       *
2232       *    {a, a}, {a, b}, {a, c}
2233       *    {b, b}, {b, c},
2234       *    {c, c}
2235       */
2236      public static <T> List<List<T>> create_combinations (int dims, int start, List<T> objs) {
2237    
2238        if (dims < 1) throw new IllegalArgumentException();
2239    
2240        List<List<T>> results = new ArrayList<List<T>>();
2241    
2242        for (int i = start; i < objs.size(); i++) {
2243          if (dims == 1) {
2244            List<T> simple = new ArrayList<T>();
2245            simple.add (objs.get(i));
2246            results.add (simple);
2247          } else {
2248            List<List<T>> combos = create_combinations (dims-1, i, objs);
2249            for (Iterator<List<T>> j = combos.iterator(); j.hasNext(); ) {
2250              List<T> simple = new ArrayList<T>();
2251              simple.add (objs.get(i));
2252              simple.addAll (j.next());
2253              results.add (simple);
2254            }
2255          }
2256        }
2257    
2258        return (results);
2259      }
2260    
2261      /**
2262       * Returns a list of lists of each combination (with repetition, but
2263       * not permutations) of integers from start to cnt (inclusive) over
2264       * arity dimensions.
2265       *
2266       * For example, create_combinations (1, 0, 2) returns:
2267       *    {0}, {1}, {2}
2268       *
2269       * And create_combinations (2, 0, 2) returns:
2270       *
2271       *    {0, 0}, {0, 1}, {0, 2}
2272       *    {1, 1}  {1, 2},
2273       *    {2, 2}
2274       */
2275      public static ArrayList<ArrayList<Integer>> create_combinations (int arity, int start, int cnt) {
2276    
2277        ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
2278    
2279        // Return a list with one zero length element if arity is zero
2280        if (arity == 0) {
2281          results.add (new ArrayList<Integer>());
2282          return (results);
2283        }
2284    
2285        for (int i = start; i <= cnt; i++) {
2286          ArrayList<ArrayList<Integer>> combos = create_combinations (arity-1, i, cnt);
2287          for (Iterator<ArrayList<Integer>> j = combos.iterator(); j.hasNext(); ) {
2288            ArrayList<Integer> simple = new ArrayList<Integer>();
2289            simple.add (new Integer(i));
2290            simple.addAll (j.next());
2291            results.add (simple);
2292          }
2293        }
2294    
2295        return (results);
2296    
2297      }
2298    
2299      /**
2300       * Returns the simple unqualified class name that corresponds to the
2301       * specified fully qualified name.  For example if qualified name
2302       * is java.lang.String, String will be returned.
2303       **/
2304      public static String unqualified_name (String qualified_name) {
2305    
2306        int offset = qualified_name.lastIndexOf ('.');
2307        if (offset == -1)
2308          return (qualified_name);
2309        return (qualified_name.substring (offset+1));
2310      }
2311    
2312      /**
2313       * Returns the simple unqualified class name that corresponds to the
2314       * specified class.  For example if qualified name of the class
2315       * is java.lang.String, String will be returned.
2316       **/
2317      public static String unqualified_name (Class<?> cls) {
2318    
2319        return (unqualified_name (cls.getName()));
2320      }
2321    
2322    
2323      // This name "human_readable" is terrible.
2324      /**
2325       * Convert a number into an abbreviation such as "5.00K" for 5000 or
2326       * "65.0M" for 65000000.  K stands for 1000, not 1024; M stands for
2327       * 1000000, not 1048576, etc.  There are always exactly 3 decimal digits
2328       * of precision in the result (counting both sides of the decimal point).
2329       */
2330      public static String human_readable (long val) {
2331    
2332        double dval = (double) val;
2333        String mag = "";
2334    
2335        if (val < 1000)
2336          ;
2337        else if (val < 1000000) {
2338          dval = val / 1000.0;
2339          mag = "K";
2340        } else if (val < 1000000000) {
2341          dval = val / 1000000.0;
2342          mag = "M";
2343        } else {
2344          dval = val / 1000000000.0;
2345          mag = "G";
2346        }
2347    
2348        String precision = "0";
2349        if (dval < 10)
2350          precision = "2";
2351        else if (dval < 100)
2352          precision = "1";
2353    
2354        return String.format ("%,1." + precision + "f" + mag, dval);
2355    
2356      }
2357    
2358    }