001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.io;  
007    
008    import fc.util.*;
009    import java.io.*;
010    import java.net.*;
011    import java.nio.*;
012    import java.nio.channels.*;
013    import java.nio.charset.*;
014    import java.util.*;
015    import java.util.regex.*;
016    import java.text.*;
017    import java.security.*;
018    
019    /** Misc. IO utilities 
020    
021    @author   hursh jain
022    @version  1.1 
023    **/
024    public final class IOUtil
025    {
026    //for internal class debugging at development time
027    private static final boolean dbg = false;
028    
029    /** System.getProperty("file.separator") for convenience **/
030    public static final String FILE_SEP = System.getProperty("file.separator");
031    
032    /** System.getProperty("path.separator") for convenience **/
033    public static final String PATH_SEP = System.getProperty("path.separator");
034    
035    /** System.getProperty("line.separator") for convenience **/
036    public static final String LINE_SEP = System.getProperty("line.separator");
037    
038    public static final int FILECOPY_OVERWRITE = 0;
039    public static final int FILECOPY_NO_OVERWRITE = 1;
040    
041    /** 
042    Ignores a directory copy command if the destination directory already
043    exists.
044    **/
045    public static final int DIRCOPY_NO_OVERWRITE = 2;
046    
047    /** 
048    Copies the existing directory and overwrites any files with
049    the same name in the destination directory. Files/directories that 
050    exist in the destination but not in the source directory are left
051    untouched.
052    **/
053    public static final int DIRCOPY_ADD_OR_OVERWRITE = 3;
054    
055    /** 
056    Copies the existing directory. If the destination directory already
057    exists, the entire target directory is first deleted and then
058    the specified directory is copied to the destination
059    **/
060    public static final int DIRCOPY_DELETE_AND_WRITE = 4;
061    
062    
063    /** number of bytes contained in a kilobyte */
064    public static final int ONE_KB = 1024;
065    
066    /** number of bytes contained in a megabyte. */
067    public static final int ONE_MB = ONE_KB * ONE_KB;
068    
069    /** number of bytes contained in a gigabyte. */
070    public static final int ONE_GB = ONE_KB * ONE_MB;
071    
072    /** number of bytes equal to 2^32 **/
073    public static final long FOUR_GB = ONE_GB * 4L;
074    
075    /**
076    Beeps by writing the beep control code to System.out
077    */
078    public static void beep() 
079      {
080      System.out.print("\007");
081      System.out.flush();
082      }
083    
084    /**
085    Copies source file (not directory) to destination. If the destination
086    already exists, then no copy is made (that is, the destination is
087    <b>not</b> overwritten and this method returns silently (an Exception
088    is not thrown).
089    
090    @param file   the source file to be copied, a java.io.File
091    @param dest   the destination file or directory.
092    @see copyFile(File, File, int)
093    */
094    public static boolean copyFile(File source, File dest) throws FileNotFoundException, IOException
095      {
096      return copyFile(source, dest, IOUtil.FILECOPY_NO_OVERWRITE);
097      }
098    
099    /**
100    Copies the source file (not directory) to the specified
101    destination file or directory. If a directory is specified as
102    the destination, then the source file is copied into that
103    directory. To specify the action when a file with the same name
104    already exists in the specified directory, use the appropriate
105    {@link #FILECOPY_OVERWRITE} or {@link #FILECOPY_NO_OVERWRITE}
106    flags.
107    If a file is specified as the destination, then the source file
108    is copied to that file. If the specified file exists already
109    then specify the appropriate overwrite flag.  
110    <p>
111    Try to use absolute path names for files and directories. Relative
112    path names can be relative either to user.dir or where the jvm was invoked
113    or some platform/jvm dependent place, so don't rely on relative paths.  
114    Copying, moving or working with files is tricky in java. Similar 
115    behavior is not defined for all platforms. For example, in WindowsNT/java1.2, 
116    aliases cannot be opened/resolved and hence cannot be copied via 
117    FileInput/Output streams. This sort of thing is best left to
118    JNI calls native code. 
119    <p>
120    This method returns <tt>true</tt> if the directory was copied
121    successfully, <tt>false</tt> otherwise. (for example, <tt>false</tt>
122    will be returned when the copy mode is not to overwrite but the
123    target file already exists).
124     
125    @param source   the source file (<b>not</b> directory) to be copied, a java.io.File
126    @param dest   the destination file or directory.
127    @param copyflag the file copy mode. {@link #FILECOPY_OVERWRITE}, {@link #FILECOPY_NO_OVERWRITE} 
128    */  
129    public static boolean copyFile(File source, File dest, int copyflag) throws FileNotFoundException, IOException
130      {
131      Argcheck.notnull(source, "copyFile(): source file argument is null"); 
132      Argcheck.notnull(dest, "copyFile(): destination file argument is null"); 
133      Argcheck.istrue((copyflag == FILECOPY_OVERWRITE || copyflag == FILECOPY_NO_OVERWRITE),"copyflag not valid"); 
134      Argcheck.isfalse(source.isDirectory(), "A directory [" + source + "] was specified for the source. This method cannot only copy normal files");
135      
136      if (dest.isDirectory()) {
137        dest = new File(dest, source.getName());
138        }
139        
140      if (dest.exists()) {
141        if (copyflag == IOUtil.FILECOPY_NO_OVERWRITE) {
142          return false;
143          }
144        }
145    
146        final FileInputStream fin = new FileInputStream(source);
147      final FileOutputStream fout = new FileOutputStream(dest);
148      final FileChannel fcin = fin.getChannel();
149      final FileChannel fcout = fout.getChannel();
150        
151        final MappedByteBuffer mbb = fcin.map(
152                  FileChannel.MapMode.READ_ONLY, 0, fcin.size());
153        
154        fcout.write(mbb);
155          
156        fcin.close();
157      fcout.close();
158      fin.close();
159      fout.close();
160      return true;
161      }
162    
163    /**
164    Calls {@link #copyFile(File, File, int)} with the 
165    {@link #DIRCOPY_ADD_OR_OVERWRITE} flag.
166    
167    @param file   the source directory to be copied
168    @param dest   the destination directory.
169    @see copyFile(File, File, int)
170    */
171    public static boolean copyDirectory(File source, File dest) throws FileNotFoundException, IOException
172      {
173      return copyDirectory(source, dest, IOUtil.DIRCOPY_ADD_OR_OVERWRITE);
174      }
175    
176    /**
177    Copies the source directory and all it's contents to the specified 
178    destination directory. A directory must be specified both for the source 
179    and the destination. 
180    <p>
181    To handle cases where the destination directory already exists
182    use the appropriate {@link #DIRCOPY_NO_OVERWRITE}
183    {@link #DIRCOPY_DELETE_AND_WRITE} and
184    {@link #DIRCOPY_ADD_OR_OVERWRITE} flags.
185    <p>
186    Try to use absolute path names for files and directories. Relative
187    path names can be relative either to user.dir or where the jvm was invoked
188    or some platform/jvm dependent place, so don't rely on relative paths.  
189    <u>Copying, moving or working with files is tricky in java</u>. Similar 
190    behavior is not defined for all platforms. For example, in WindowsNT/java1.2, 
191    aliases cannot be opened/resolved and hence cannot be copied via 
192    FileInput/Output streams. This sort of thing is best left to
193    JNI calls to POSIX or to platform specific code. 
194    <p>
195    This method returns <tt>true</tt> if the directory was copied
196    successfully, <tt>false</tt> otherwise. (for example, <tt>false</tt>
197    will be returned when the copy mode is not to overwrite but the
198    target directory already exists).
199    
200    @param source   the source directory (<b>not</b> file) to be copied, a java.io.File
201    @param dest   the destination file or directory.
202    @param copyflag the dir copy mode. {@link #DIRCOPY_NO_OVERWRITE}, {@link #DIRCOPY_ADD_OR_OVERWRITE} 
203    */  
204    public static boolean copyDirectory(File source, File dest, int copyflag) 
205    throws IOException
206        {
207      Argcheck.notnull(source, "copyDirectory(): source file argument is null"); 
208      Argcheck.notnull(dest, "copyDirectory(): destination file argument is null"); 
209      Argcheck.istrue((copyflag == DIRCOPY_NO_OVERWRITE || copyflag == DIRCOPY_ADD_OR_OVERWRITE || copyflag == DIRCOPY_DELETE_AND_WRITE), "copyflag not valid"); 
210    
211      if (source.exists()) {
212        Argcheck.istrue(source.isDirectory(), "IOUtil.copyDirectory(): A file was specified for the source, need a directory not a file");
213        }
214        
215      if (dest.exists()) 
216        {
217        if (dbg) System.out.println("IOUtil.copyDirectory(): destination '" + dest + "' exists");
218        
219        if ( ! dest.isDirectory() ) {
220          if (dbg) System.out.println("IOUtil.copyDirectory('" + source + "','" + dest + "'): A file was specified for the destination, need a directory not a file");
221          return false;
222          }
223        
224        if (copyflag == IOUtil.DIRCOPY_NO_OVERWRITE) {
225          System.out.println("IOUtil.copyDirectory(): Incompatible flag DIRCOPY_NO_OVERWRITE specified, returning false");
226          return false;
227          }
228        if (copyflag == IOUtil.DIRCOPY_DELETE_AND_WRITE) 
229          {
230          boolean good = deepDelete(dest);
231          if (! good) 
232            throw new IOException("IOUtil.copyDirectory, flag=DIRCOPY_DELETE_AND_WRITE, cannot delete the destination directory:" + dest);
233          }
234        } 
235      else { //dest dir does not exist
236        if (dbg) System.out.println("IOUtil.copyDirectory(): destination dir '" + dest + "' does not exist. Creating..");   
237        boolean good = dest.mkdirs();
238        if (! good) {
239          if (dbg) System.out.println("IOUtil.copyDirectory(): could not make directory '" + dest + "' ; returning false");
240          return false;
241          }
242        }
243      
244      String files[] = source.list(); //does not return "." or ".."
245      
246      boolean copiedOne = false;
247      boolean copiedAll = true;
248      for(int i = 0; i < files.length; i++)
249        {
250        File source_f = new File(source, files[i]);
251        File dest_f = new File(dest, files[i]);
252        if(source_f.isDirectory()) {
253          if (dbg) System.out.println("IOUtil.copyDirectory(): recursive copy directory call: '" + source_f + "' to '" + dest_f + "'");
254          copiedOne = copyDirectory(source_f, dest_f, copyflag);
255          }
256        else {
257          if (dbg) System.out.println("IOUtil.copyDirectory(): copying file: '" + source_f + "' to '" + dest_f + "'");
258          copiedOne = copyFile(source_f, dest_f, IOUtil.FILECOPY_OVERWRITE);
259          }
260            if (! copiedOne)
261          copiedAll = false;
262        }
263      if (dbg) System.out.println("IOUtil.copyDirectory: returning: " + copiedAll);
264      return copiedAll;
265        } //~copyDirectory
266    
267    /** 
268    Copies all data from the specific input stream to the specified output stream.
269    Closes both streams after it is finished.
270    
271    @param  in  the InputStream
272    @param  out the OutputStream
273    **/
274    public static void copyStream(InputStream in, OutputStream out) 
275                              throws IOException
276      {
277      final BufferedInputStream bin = bufferStream(in);
278      final BufferedOutputStream bout = bufferStream(out);
279      int i = 0;
280      while ( (i = bin.read()) > -1) {
281        bout.write(i);  
282        }
283      //FilterStream (like bufferedinputstream etc) close all internal
284      //streams when they are closed too !
285      bin.close();
286      bout.close();   
287      } 
288    
289    /** 
290    Alphabetizes the specified list by the filename. Only the
291    filename is considered and not the path name (if any). The 
292    collection should contain one of: 
293    <ul>
294      <li> <tt>java.io.File</tt>
295      <li> <tt>String[]</tt>
296      <li> <tt>Object</tt>
297    </ul>
298    Any/all of these can be contained in the specified list at the
299    same time. If a <tt>String[]</tt> is found, the <b>0</b>th element
300    (i.e., (String[] foo)[0]) is used for comparison purposes. The
301    list is sorted by the default {@link String#compareTo(String)} implementation 
302    of <tt>String</tt>.
303    
304    @param  list  the list to be sorted
305    **/
306    public static void sortByFileName(List c) {
307      Collections.sort(c, new Comparator() {
308        public int compare(Object o1, Object o2) {
309          return getStr(o1).compareTo(getStr(o2));    
310          }
311        private String getStr(Object o) {
312          String str = null;
313          if ( o instanceof File )
314            str = ((File)o).getName();
315          else if (o instanceof String) 
316            str = (String) o;
317          else 
318            str = (o!=null)? o.toString() : null;
319          return str;
320          }   
321        });
322      }
323    
324    /**
325    This method recursively removes a specified file or recursively 
326    removes a specified directory. It is needed (as of JDK 1.4) because
327    {@link File#delete} lacks the ability to delete a directory if the 
328    directory is not empty. 
329    <p>
330    Internally, this method delegates to {@link File#delete}, so if
331    {@link File#delete} follows sym links, then so will this method.
332    Be careful !
333    
334    @param  file  the file or directory to be removed
335    @return     <tt>true</tt> on success, <tt>false</tt> otherwise. Also returns false if
336            the specified file or directory does not exist.
337    **/
338    public static boolean deepDelete(File f)  
339      {
340      Argcheck.notnull(f, "File was null");
341      if (dbg) System.out.println("deepDelete(): deleting: " + f);
342      
343      boolean ok = true;
344    
345      if (! f.exists()) 
346        return false;
347    
348      if (f.isFile()) {
349        ok = f.delete();
350        return ok;
351        }
352    
353      //f is a directory
354      File[] files = f.listFiles();  //does not return "." or ".."
355      boolean subok = false;
356    
357      //1. delete sub directories
358      for (int n = 0; n < files.length; n++) {
359        subok = deepDelete(files[n]);
360        if (! subok) ok = false;
361        } 
362      
363      //2. delete current directory
364      subok = f.delete();
365      if (! subok) ok = false;
366      
367      return ok;  
368      }
369    
370    /**
371    Gets the total size for a directory and all of it's contents. If
372    the specified argument is a regular file, returns the size of that
373    file itself.
374    
375    @param  dir   the target dir
376    @return     the directory or file size in bytes
377    **/
378    public static long dirSize(File dir) 
379      {
380      Argcheck.notnull(dir, "File was null");
381      long size = 0;
382    
383      if (! dir.exists()) 
384        return 0;
385    
386      if (dir.isFile()) {
387        return dir.length();    
388        }
389    
390      File[] files = dir.listFiles();  //does not return "." or ".."
391    
392      for (int n = 0; n < files.length; n++) 
393        size += dirSize(files[n]);
394        
395      return size;
396      }
397    
398    /** 
399    Buffers and returns the specified InputStream, if it is not already buffered.
400    Does not buffer an already buffered stream but returns it as is.
401    
402    @param in the input stream to be buffered
403    @return the buffered stream
404    */
405    public static BufferedInputStream bufferStream(InputStream in)
406      {
407      Argcheck.notnull(in, "InputStream was null");
408      BufferedInputStream bin;
409      if (! (in instanceof BufferedInputStream)) {
410        bin = new BufferedInputStream(in);    
411        }
412      else {
413        bin = (BufferedInputStream) in;
414        }
415      return bin;
416      }
417    
418    /** 
419    Buffers and returns the specified OutputStream, if it is not already buffered.
420    Does not buffer an already buffered stream but returns it as is.
421    
422    @param out  the output stream to be buffered
423    @return the buffered stream
424    
425    **/
426    public static BufferedOutputStream bufferStream(OutputStream out)
427      {
428      Argcheck.notnull(out, "OutputStream was null");
429      BufferedOutputStream bout;
430      if (! (out instanceof BufferedOutputStream)) {
431        bout =  new BufferedOutputStream(out);    
432        }
433      else {
434        bout = (BufferedOutputStream) out;
435        }
436      return bout;
437      }
438      
439    public static BufferedReader bufferReader(Reader in) 
440      {
441      Argcheck.notnull(in, "Reader was null");
442      BufferedReader bin;
443      if ( ! (in instanceof BufferedReader)) {
444        bin = new BufferedReader(in);
445        }
446      else {
447        bin = (BufferedReader) in;
448        }
449      return bin;
450      }
451        
452    public static PrintStream toPrintStream(OutputStream out) 
453      {
454      Argcheck.notnull(out, "OutputStream was null");
455      if ( ! (out instanceof PrintStream)) {
456        out = new PrintStream(out);
457        }
458      return (PrintStream) out;
459      }
460      
461    public static PrintWriter toPrintWriter(Writer out) 
462      {
463      Argcheck.notnull(out, "Writer was null");
464      if ( ! (out instanceof PrintWriter)) {
465        out = new PrintWriter(out);
466        }
467      return (PrintWriter) out;
468      }
469    
470    public static BufferedWriter bufferWriter(Writer out) 
471      {
472      Argcheck.notnull(out, "Writer was null");
473      BufferedWriter bout;
474      if ( ! (out instanceof BufferedWriter)) {
475        bout = new BufferedWriter(out);
476        }
477      else {
478        bout = (BufferedWriter) out;
479        }
480      return bout;
481      }
482    
483    /**
484    Convenience method to print the contents of a java.util.Property object
485    to a String (using the default platform encoding).
486    
487    **/
488    public static String propertiesToString(Properties props)
489      {
490      String temp = null;
491      final ByteArrayOutputStream bout = new ByteArrayOutputStream();
492      final PrintStream pout = new PrintStream(bout);
493      props.list(pout);
494      pout.flush();
495      temp = bout.toString();
496      pout.close();
497      return temp;
498      }
499    
500    private static String defaultEncoding = null;
501    
502    /**
503    Returns the default encoding used by the current platform. Returns
504    <tt>null</tt> is the default encoding cannot be determined.
505    **/
506    public static String getDefaultEncoding() 
507      {
508      if (defaultEncoding != null) 
509        return defaultEncoding;
510      String de = null;
511      try {
512        final InputStream in = ClassLoader.getSystemResourceAsStream("fc/io/IOUtil.class");
513        final InputStreamReader defaultReader = new InputStreamReader(in);
514        de = defaultReader.getEncoding();
515        defaultEncoding = de;
516        if (dbg) System.out.println("IOUtil.getDefaultEncoding() = " + de);
517        }
518      catch (Exception e) {
519        e.printStackTrace();  
520        }
521      return de;
522      }
523    
524    
525    /**
526    Returns the contents of an entire file as a List of individual lines. If the specified
527    file does not exist or have no content return an empty List.
528    
529    @param  instream    the stream to be read 
530    @param  trim      if <tt>true</tt>, any leading or trailing blank lines are trimmed are ignored.
531    @param  comment_chars Regex of comment chars. Any lines that start with this (or have leading spaces 
532                and then start with this) are ignored. (example: <tt>#</tt> or <tt>#|//</tt>)
533    */
534    public static List fileToLines(InputStream instream, boolean trim, String comment_chars) throws IOException
535      {
536      BufferedReader in = new BufferedReader(new InputStreamReader(instream, "UTF-8"));
537      
538      List list = new ArrayList();
539      String line = null;
540      Pattern pat = null; 
541      Matcher m = null;
542      if (comment_chars != null) {
543        pat = Pattern.compile("^[ \\t]*(" + comment_chars + ")+");
544        }
545        
546      while ( (line = in.readLine()) != null)
547        {
548        if (trim) {
549          line = line.trim();  //this gets rid of spaces, empty newlines, etc
550          if (line.length() == 0) {
551            continue;
552            }
553          }
554          
555        if (pat != null)
556          {
557          m = pat.matcher(line);
558          if (m.find()) {
559            continue;
560            }
561          }
562        list.add(line);
563        }
564      return list;
565      }
566    
567    /**
568    Returns the contents of an entire file as a List of individual lines. If the specified
569    file does not exist or have no content return an empty List.
570    
571    @param  File      the file to be read ("UTF-8" encoding is used)
572    @param  trim      if <tt>true</tt>, any leading or trailing blank lines are trimmed are ignored.
573    @param  comment_chars Regex of comment chars. Any lines that start with this (or have leading spaces 
574                and then start with this) are ignored. (example: <tt>#</tt> or <tt>#|//</tt>)
575    */
576    public static List fileToLines(File file, boolean trim, String comment_chars) throws IOException
577      {
578      return fileToLines(new FileInputStream(file), trim, comment_chars);
579      }
580      
581    /**
582    Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
583    and lines beginning with the following characters are ignored: <tt>#</tt> and <tt>//</tt>
584    
585    @param  file    the file to be read ("UTF-8" encoding is used)
586    */
587    public static List fileToLines(File file) throws IOException
588      {
589      return fileToLines(file, true, "#|//");
590      }
591    
592    /**
593    Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
594    and lines beginning with the following characters are ignored: <tt>#</tt> and <tt>//</tt>
595    
596    @param  filename  the file to be read ("UTF-8" encoding is used)
597    */
598    public static List fileToLines(String filename) throws IOException
599      {
600      return fileToLines(new File(filename));
601      }
602      
603    /**
604    Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
605    and lines beginning with the following characters are ignored: <tt>#</tt> and <tt>//</tt>
606    
607    @param  in  the input stream to be read
608    */
609    public static List fileToLines(InputStream in) throws IOException
610      {
611      return fileToLines(in, true, "#|//");
612      } 
613      
614    /** 
615    Returns the contents of an entire file as a String. If the specified
616    file does not exist returns <tt>null</tt>. Files that exist but have no
617    content return an empty String.
618    <p>
619    <b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
620    less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
621    an <tt>IOException</tt> will be thrown.
622    <br>
623    <b>Note 2:</b> The files is converted into a String using an encoding
624    that is determined programmatically (from the filesystem). This may
625    not be totally reliable but there is no way around this because JDK 1.4
626    provides <b>no</b> way to easily get the default platform encoding.
627    Uses the <tt>ISO_8859_1</tt> encoding as a fallback measure, if the
628    default encoding cannot be determined.
629    
630    @param  filename  the file to be read
631    @param  trim    if <tt>true</tt>, any trailing whitespace is trimmed from the file's end.
632    **/
633    public static String fileToString(File file, boolean trim) throws IOException
634        {
635      byte[] buf = fileToByteArray(file);
636      if (buf == null)
637        return null;
638      
639      //there is no way to convert a byte buffer to a String
640    
641      //because we cannot get a Charset that uses the default platform
642      //encoding in JDK 1.4.0. So we are going to IOUtil.getDefaultEncoding 
643      //method (which is a workaround) to get the default platform encoding.
644      
645      //update: instead of default encoding, arrayToCharBuffer will UTF-8 if 
646      //encoding is not specified.
647    
648      // resultstr will be "" if buf contains 0 chars (it can't be null 
649      // if we have reached this point)
650    
651      String resultstr = arrayToCharBuffer(buf).toString(); 
652      if (trim) {
653        //might trigger g.c if string size is large
654        resultstr = resultstr.trim();
655        }
656      return resultstr;
657        }
658    
659    
660    /** 
661    Returns the contents of an entire file as a String. If the specified
662    file does not exist returns <tt>null</tt>. Files that exist but have no
663    content return an empty String.
664    <p>
665    <b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
666    less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
667    an <tt>IOException</tt> will be thrown.
668    <br>
669    <b>Note 2:</b> The files is converted into a String using an encoding
670    that is determined programmatically (from the filesystem). This may
671    not be totally reliable but there is no way around this because JDK 1.4
672    provides <b>no</b> way to easily get the default platform encoding.
673    Uses the <tt>ISO_8859_1</tt> encoding as a fallback measure, if the
674    default encoding cannot be determined.
675    
676    @param  file  the absolute path to the file name to be read
677    @param  trim  if <tt>true</tt>, any trailing whitespace is trimmed from the file's end.
678    **/
679    public static String fileToString(String filename, boolean trim) throws IOException
680      {
681      return fileToString(new File(filename), trim);
682      }
683    
684    /** 
685    Calls {@link #fileToString(String, boolean)} with trim being
686    <tt>false</tt> (that is, files are not trimmed at their trailing end).
687    
688    @param  filename  the absolute path to the file name to be read
689    **/
690    public static String fileToString(String filename) throws IOException
691        {
692        return fileToString(filename, false);
693      }
694    
695    /** 
696    Calls {@link #fileToString(String, boolean)} with trim being
697    <tt>false</tt> (that is, files are not trimmed at their trailing end).
698    
699    @param  file  the file to be read
700    **/
701    public static String fileToString(File file) throws IOException
702        {
703        return fileToString(file, false);
704      }
705    
706    
707    /**  
708    Returns the contents of an entire file as a <tt>byte[]</tt>. If the specified
709    file does not exist returns <tt>null</tt>. 
710    <p>
711    <b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
712    this method is limited to files less than or equal to 2^32 bytes. If 
713    the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
714    will be thrown.
715    
716    @param    filename  the absolute path to the file name to be read
717    @return ByteBuffer  contains the bytes for that file
718    **/
719    public static byte[] fileToByteArray(String filename) throws IOException
720      {
721      return fileToByteArray(new File(filename));
722      }
723    
724    /**  
725    Returns the contents of an entire file as a <tt>byte[]</tt>. If the specified
726    file does not exist returns <tt>null</tt>. 
727    <p>
728    <b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
729    this method is limited to files less than or equal to 2^32 bytes. If 
730    the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
731    will be thrown.
732    
733    @param    file    the file to be read
734    @return byte[]    contains the bytes for that file
735    **/
736    public static byte[] fileToByteArray(File file) throws IOException
737      {
738      if (dbg) System.out.println("ENTER fileToByteBuffer(" + file + ")");
739    
740      Argcheck.notnull(file);
741      
742      if ( ! file.exists() )
743        return null;
744    
745      if (dbg) System.out.println("file '" + file + "' exists");
746      
747      long longfsize = file.length();
748      if (dbg) System.out.println("'" + file + "' size = " + longfsize);
749      
750      if ( longfsize > FOUR_GB )
751        throw new IOException("File size of " + longfsize + " too large for this method");  
752    
753      FileInputStream fin = new FileInputStream(file);
754      int fsize = (int) longfsize;
755      byte[]  buf = new byte[fsize];
756    
757      try {
758        int read, pos = 0;
759        while (pos < fsize) 
760          {
761          /* Usually, this will read everything the first time */
762          read = fin.read(buf, pos, fsize - pos);
763          pos += read;
764          if (read < 0)
765            break;
766          }
767    
768        if (dbg) System.out.println("Read file byte[] = " + buf.length + " bytes");
769        
770        if (pos != fsize)
771              throw new IOException( "Can't read entire file, filesize = " + fsize + ", read = " + pos);
772    
773        if (dbg) System.out.println("EXIT fileToByteBuffer(" + file + ")");
774        }
775      finally {
776        fin.close(); 
777        }
778    
779      return buf;
780      }
781      
782    /**  
783    Returns the contents of an entire file as a ByteBuffer backed by mapping the
784    file to memory. If the specified file does not exist returns <tt>null</tt>.
785    Mapped files do <b>not</b> have have a accesible backing array and the
786    <tt>ByteBuffer.hasArray()</tt> will be <tt>false</tt>. See the {@link
787    java.nio.MappedByteBuffer} documentation about concurrent modification or
788    deletion of files that are mapped into memory.
789    <p>
790    The ByteBuffer returned by this method will have <tt>{@link
791    ByteBuffer#rewind()} </tt> called on it before it is returned.
792    <p>
793    <b>Note 1:</b> This method is limited to files less than 2^32 bytes, since
794    ByteBuffers cannot be greater than this size. If the specified file is greater 
795    than 2^32 bytes, an <tt>IOException</tt> will be thrown.
796    
797    @param    file    the file to be read
798    @return ByteBuffer  contains the bytes for that file
799    **/
800    public static ByteBuffer fileToByteBuffer(File file) throws IOException
801      {
802      if (dbg) System.out.println("ENTER fileAsByteBuffer(" + file + ")");
803      Argcheck.notnull(file);
804      long fsize = 0;
805    
806      if ( ! file.exists() )
807        return null;
808    
809      if (dbg) System.out.println("file '" + file + "' exists");
810      
811      fsize = file.length();
812    
813      if (dbg) System.out.println("'" + file + "' size = " + fsize);
814      
815      if ( fsize > FOUR_GB)
816        throw new IOException("File size of " + file.length() + " too large for this method"); 
817    
818      FileChannel fcin = new FileInputStream(file).getChannel();
819      
820      BufferedReader reader = null;
821      final ByteBuffer bufin = fcin.map(FileChannel.MapMode.READ_ONLY, 0, fsize);
822      
823      if (dbg) System.out.println("File ByteBuffer = " + bufin);
824    
825      //This is very important and easy to forget -- rewind the buffer !!!
826      
827      bufin.rewind();
828    
829      if (dbg) System.out.println("EXIT fileAsByteBuffer(" + file + ")");
830      return bufin;
831      }
832    
833    /**  
834    Returns the contents of an entire file as a <tt>char[]</tt>. If the specified
835    file does not exist returns <tt>null</tt>. 
836    <p>
837    <b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
838    this method is limited to files less than or equal to 2^32 bytes. If 
839    the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
840    will be thrown.
841    
842    @param    file    the file to be read
843    @param    encoding  the name of the character encoding to use. 
844                Specify <tt>null</tt> to use UTF-8 encoding.
845    @return char[]      contains the chars for that file
846    **/
847    public static char[] fileToCharArray(File file, String encoding) 
848    throws IOException
849      {
850      Argcheck.notnull(file);
851      
852      if ( ! file.exists() )
853        return null;
854    
855      if (dbg) System.out.println("file '" + file + "' exists");
856      
857      long longfsize = file.length();
858      if (dbg) System.out.println("'" + file + "' size = " + longfsize);
859      
860      if ( longfsize > FOUR_GB )
861        throw new IOException("File size of " + longfsize + " too large for this method");  
862    
863      FileInputStream fin = new FileInputStream(file);
864      
865      if (encoding == null) {
866        encoding = "UTF-8";
867        }
868        
869      final Reader reader = new InputStreamReader(fin, encoding); 
870      int fsize = (int) longfsize;
871      char[]  buf = new char[fsize];
872    
873      try {
874        int read, pos = 0;
875        while (pos < fsize) 
876          {
877          /* Usually, this will read everything the first time */
878          read = reader.read(buf, pos, fsize - pos);
879          pos += read;
880          if (read < 0) { //EOF
881            break;
882            }
883          }
884    
885        if (dbg) System.out.println("Read file char[] = " + buf.length + " count");
886        
887        if (dbg && pos != fsize) {
888          System.out.println("File: " +  file.getAbsolutePath() 
889            + " has bytes [" + fsize + "], read [" + pos + "] chars\n"
890            + "This is expected since byte->char will loose characters for non-ascii files. Currently using [" 
891            + encoding + "] to read this file"
892            );
893          }
894        }
895      finally {
896        fin.close(); 
897        }
898    
899      return buf;
900      }
901    
902    /*
903    Reads the entire Stream and returns all read data as a 
904    <tt>char[]</tt>. If no data is available, returns an empty char[].
905    */
906    public static char[] readerToCharArray(Reader reader) 
907    throws IOException
908      {
909      Argcheck.notnull(reader);
910    
911      int buffer_size = 1024;
912      char[] buf = new char[buffer_size];
913      if (dbg) System.out.println("readerToCharArray(), block=yes");
914    
915      final CharArrayWriter cout = new CharArrayWriter(buffer_size);
916      int read = 0;
917      while (true) {
918        read = reader.read(buf, 0, buffer_size);  
919        if (read == -1)
920          break;  
921        cout.write(buf, 0, read);
922        }
923    
924      return cout.toCharArray();    
925      }
926    
927    
928    /** 
929    Converts the specified byte array into a CharBuffer using the specified
930    encoding. The returned CharBuffer can be directly used in statements such
931    as <tt>System.out.println</tt> to print it's contents,
932    <p>
933    This method returns <tt>null</tt> if the specified array is <tt>null</tt>
934    or if the specified encoding is <tt>null</tt>.
935    
936    @param  array   the array to convert
937    @param  encoding  the {@link java.nio.charset.Charset charset} encoding to use to     
938              convert bytes into chars
939    **/
940    public static CharBuffer arrayToCharBuffer(byte[] array, String encoding) 
941      {
942      if ( (array == null) || (encoding == null))
943        return null;  
944        
945      Charset cset = Charset.forName(encoding);
946      CharBuffer cbuf = cset.decode(ByteBuffer.wrap(array));
947      return cbuf;
948      }
949    
950    /** 
951    Convenience method that delegates to {@link #arrayToCharBuffer(byte[],
952    String)} using UTF-8 encoding by default.
953    **/ 
954    public static CharBuffer arrayToCharBuffer(byte[] array) 
955      {
956      //String enc = IOUtil.getDefaultEncoding();
957      //enc = (enc != null) ? enc : "ISO-8859-1";
958      //UTF-8 is safer overall
959      return arrayToCharBuffer(array, "UTF-8");
960      } 
961    
962    /**
963    Reads the entire InputStream and returns all read data as a 
964    <tt>byte[]</tt>. If no data is available, returns <tt>null</tt>.
965    
966    @param  in    the InputStream to read
967    @param  block   if <tt>true</tt>, this method will block until all 
968            available data from the specified input stream
969            has been read. If <tt>false</tt>, this method will
970            read and return as much data as currently is available 
971            is read without blocking. The available amount is 
972            that returned by the available() method of the specified 
973            input stream.
974    
975    @throws NegativeArraySizeException  if the specified input stream returns
976                      a negative number for available()       
977    **/
978    public static byte[] inputStreamToByteArray(InputStream in, boolean block) 
979    throws IOException
980      {
981      Argcheck.notnull(in, "InputStream was null");
982    
983      final BufferedInputStream bin = bufferStream(in);
984      
985      if (! block) {
986        int buffer_size = bin.available();
987        if (dbg) System.out.println("inputStreamToByteArray(), block=no, buffersize=" + buffer_size);
988        byte[] buf = new byte[buffer_size];
989        int read = 0;
990        int pos = 0;
991        while (read < buffer_size) {
992          read += bin.read(buf, pos, buffer_size - read);
993          pos = read + 1;
994          }
995        if (dbg) System.out.println("inputStreamToByteArray(), returning buf=" + buf.length + " bytes");
996        return buf;
997        }
998      
999      //block
1000      int buffer_size = 1024;
1001      byte[] buf = new byte[buffer_size];
1002      if (dbg) System.out.println("inputStreamToByteArray(), block=yes");
1003    
1004      final ByteArrayOutputStream bout = new ByteArrayOutputStream(buffer_size);
1005      int read = 0;
1006      while (true) {
1007        read = bin.read(buf, 0, buffer_size); 
1008        if (read == -1)
1009          break;  
1010        bout.write(buf, 0, read);
1011        }
1012      //if size() is 0, toByteArray returns an array of size 0. we
1013      //return null instead.
1014      if (bout.size() == 0) {
1015        return null;
1016        }
1017      return bout.toByteArray();    
1018      }
1019    
1020    /**
1021    Calls inputStreamToByteArray(in, <tt>true</tt>)
1022    */
1023    public static byte[] inputStreamToByteArray(InputStream in) throws IOException
1024      {
1025      return inputStreamToByteArray(in, true);  
1026      }
1027    
1028    
1029    /**
1030    Reads the entire InputStream and returns all read data as a 
1031    <tt>String</tt> (using the default platform encoding). If
1032    no data is available, returns <tt>null</tt>. The specified input
1033    stream is <u>not</u> closed.
1034    
1035    @param  in    the InputStream to read
1036    @param  block   if <tt>true</tt>, this method will block until all 
1037            available data from the specified input stream
1038            has been read. If <tt>false</tt>, this method will
1039            read and return as much data as currently is available 
1040            is read without blocking. The available amount is 
1041            that returned by the specified input stream.
1042    
1043    @throws NegativeArraySizeException  if the specified input stream returns
1044                      a negative number for available()
1045    **/
1046    public static String inputStreamToString(InputStream in, boolean block) 
1047    throws IOException
1048      {
1049      final byte[] buf = inputStreamToByteArray(in, block);
1050      if (buf == null)  
1051        return null;
1052      return new String(buf);   
1053      }   //~inputStreamToString
1054    
1055    
1056    
1057    /**
1058    Reads the entire InputStream and returns all read data as a 
1059    <tt>String</tt> (using the specified platform encoding). If
1060    no data is available, returns <tt>null</tt>. The specified input
1061    stream is <u>not</u> closed.
1062    <p>
1063    This method will block until all available data from the specified 
1064    input stream has been read.
1065    
1066    @param  in      the input stream to read
1067    @param  encoding  the {@link java.nio.charset.Charset} encoding name to use to
1068              convert bytes into chars
1069    */
1070    public static String inputStreamToString(InputStream in, String encoding) 
1071    throws IOException
1072      {
1073      final byte[] buf = inputStreamToByteArray(in, true);
1074      if (buf == null)  
1075        return null;
1076      return new String(buf, encoding);
1077      }
1078    
1079    
1080    /**
1081    Calls inputStreamToString(in, <tt>true</tt>)
1082    */
1083    public static String inputStreamToString(InputStream in) throws IOException
1084      {
1085      return inputStreamToString(in, true);
1086      }
1087    
1088    
1089    /**
1090    Convenience method to print the stack trace of an Exception (or Throwable)
1091    to a String (using the default platform encoding). (The <tt>getMessage()</tt>
1092    method of a Throwable does not print the entire stack trace).
1093    **/
1094    public static String throwableToString(final Throwable e)
1095      {
1096      Argcheck.notnull(e, "The specified exception object was null");
1097      String temp = null;
1098      final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1099      final PrintStream pout = new PrintStream(bout);
1100      e.printStackTrace(pout);
1101      pout.flush();
1102      temp = bout.toString();
1103      pout.close();
1104      return temp;
1105      }
1106    
1107    /**
1108    Convenience method that returns the current execution stack trace as a String.
1109    (using the default platform encoding).
1110    **/
1111    public static String stackTrace()
1112      {
1113      String temp = null;
1114      final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1115      final PrintStream pout = new PrintStream(bout);
1116      pout.println("==================== Debug Stack Trace ======================");
1117      new Exception().printStackTrace(pout);
1118      pout.println("=============================================================");
1119      pout.flush();
1120      temp = bout.toString();
1121      pout.close();
1122      return temp;
1123      }
1124    
1125    /** 
1126    Calls {@link #fileSizeToString(long)} and truncates the 
1127    size to fit in the specified number of digits. For example,
1128    a file size of <tt>4.455 KB</tt> and a length of 2 will 
1129    return <tt>4.45 KB</tt>
1130    
1131    @param  filesize  the size of the file in bytes
1132    @param  length    the max number of digits <b>after</b>
1133              the decimal point
1134    */
1135    public static String fileSizeToString(long filesize, int length)
1136      {
1137      NumberFormat nf = NumberFormat.getInstance(); 
1138      nf.setMaximumFractionDigits(length);
1139      return fileSizeToStringImpl(nf, filesize);
1140      }
1141    
1142    
1143    /** 
1144    Converts the specified file size into a human readable description. 
1145    Similar to the <tt>"--human-readable"</tt> flag found in various 
1146    GNU programs.
1147    
1148    @param  filesize  the size of the file in bytes
1149    */
1150    public static String fileSizeToString(long filesize)
1151      {
1152      NumberFormat nf = NumberFormat.getInstance(); 
1153      return fileSizeToStringImpl(nf, filesize);    
1154      }
1155      
1156    private static final String fileSizeToStringImpl(
1157              NumberFormat nf, long filesize)
1158      {
1159      StringBuffer buf = new StringBuffer(32);
1160      if (filesize > ONE_GB) 
1161            buf.append(nf.format(filesize / ONE_GB)).append(" GB");
1162      else if (filesize > ONE_MB) 
1163        buf.append(nf.format( filesize / ONE_MB)).append(" MB");
1164      else if (filesize > ONE_KB)
1165        buf.append(nf.format(filesize / ONE_KB)).append(" KB");
1166      else    
1167        buf.append(nf.format(filesize)).append(" bytes");
1168      
1169      return buf.toString();
1170      }
1171    
1172    /**
1173    Converts a string file size into a number denoting the equivalent bytes. 
1174    For example:
1175    <pre>
1176    34K, 34KB --> 34 bytes
1177    34M, 34megabytes --> 34 * 1024 bytes
1178    </pre>
1179    Allows numbers to end with <tt>k..., m..., g...., b....</tt> or no suffix 
1180    at all. Suffixes are case insensitive. 
1181    */
1182    public static long stringToFileSize(String str)
1183      {
1184      Argcheck.notnull(str, "the specified string was null");
1185      
1186      str = str.replace(" ",""); //remove all leading, trailing, embedded spaces
1187      
1188      int pos = 0;  //this is slighty easier than str.indexOf
1189      for (int n = 0; n < str.length(); n++) 
1190        {
1191        char c = str.charAt(n);
1192        switch (c) {
1193          case 'g': case 'G':
1194            return Long.parseLong(str.substring(0,n)) * ONE_GB; 
1195          case 'm': case 'M':
1196            return Long.parseLong(str.substring(0,n)) * ONE_MB; 
1197          case 'k': case 'K':
1198            return Long.parseLong(str.substring(0,n)) * ONE_KB; 
1199          case 'b': case 'B':
1200            return Long.parseLong(str.substring(0,n)); 
1201          default: 
1202            //nothing to do
1203          }
1204        }
1205      return Long.parseLong(str);
1206      }
1207    
1208    /**
1209    Returns the SHA-1 hash of the specified byte buffer.
1210    <p>
1211    This method is <b>not</b> thread safe (as far as I can tell, since it uses
1212    <code>MessageDigest.getInstance</code>, but <i>if</i> that is thread safe, then
1213    this method is thread safe). The invoker should invoke it in a thread safe way.
1214    */
1215    public static final String sha1hash(byte[] buf) throws NoSuchAlgorithmException
1216      {
1217      MessageDigest md = MessageDigest.getInstance("SHA-1");
1218      byte[] digest = md.digest(buf);
1219      StringBuilder sb = new StringBuilder();
1220      for (byte b: digest)
1221        {
1222        String hex = Integer.toHexString((int)0xff & b);
1223        if (hex.length()==1) sb.append("0");
1224        sb.append(hex);
1225        }
1226    
1227      return sb.toString();
1228      }
1229    
1230    
1231    /** 
1232     Get the InputStream for the specified resource (in the same directory as from where the specified class was loaded). 
1233     Returns null if not found. 
1234     */
1235    public static InputStream getClassResource(Class clazz, String resource_name) throws IOException
1236      {
1237      if (clazz == null) {
1238        return null;
1239        }
1240      
1241      if (resource_name.startsWith("/")) {
1242        resource_name = resource_name.substring(1,resource_name.length());
1243        }
1244      InputStream in = clazz.getResourceAsStream(resource_name);
1245      return in;
1246        }
1247      
1248    /**
1249    Usage:
1250    java IOUtil args where args are:
1251      -file full-path-to-file 
1252          [for fileToString and other tests]
1253      
1254    **/
1255    public static void main(String[] args) throws Exception
1256      {
1257      Args myargs = new Args(args);
1258      String fts = myargs.getRequired("file");
1259      new Test(fts);
1260      }
1261    
1262    
1263    /**
1264    Unit Test History   
1265    <pre>
1266    Class Version Tester  Status      Notes
1267    1.1       hj    still-testing limited testing only, more needs to be done
1268    </pre>
1269    */
1270    static private class Test {
1271    Test(String fileToString) throws Exception
1272      {
1273      java.io.File sourcefile = new java.io.File("testsourcefile");
1274      java.io.File sourcedir = new java.io.File("testsourcedir"); 
1275      java.io.File destfile = new java.io.File("testdestfile"); 
1276      java.io.File destdir = new java.io.File("testdestdir"); 
1277    
1278      String dir1 = "foo";
1279      String dir2 = "foo" + FILE_SEP;     
1280      String f = "f1";
1281      //String r1 = IOUtil.makeFilePath(dir1,f);
1282      //String r2 = IOUtil.makeFilePath(dir2,f);
1283      for(long n = 2; n < 4000000001L; n *= 1000) {
1284        System.out.println("file size " + n + " = " 
1285                  + IOUtil.fileSizeToString(n));
1286        }
1287      
1288      System.out.println("Platform encoding, via file.encoding: " + System.getProperty("file.encoding"));
1289      System.out.println("Platform encoding: " + IOUtil.getDefaultEncoding());
1290      System.out.println("--- fileToString('"+ fileToString + "') -----");
1291      String filestr = fileToString(fileToString);
1292      System.out.println("file '" + fileToString + "' read into String"); 
1293      System.out.println("String length = " + filestr.length());
1294      System.out.println("String data = ");
1295      System.out.println(filestr);
1296    
1297      List linelist = fileToLines(new File(fileToString));
1298      System.out.println("--- fileToList('"+ fileToString + "') -----");
1299      for (int n = 0; n < linelist.size(); n++) {
1300        System.out.println("[line:" + n + "]: " + linelist.get(n));
1301        }
1302    
1303      FileInputStream fin = new FileInputStream(fileToString);
1304      System.out.println("--- inputStreamToString('"+ fileToString + "') -----");
1305      String str = inputStreamToString(fin, false);
1306      System.out.println(str);
1307      System.out.println(fileToString + ", size=" + str.length());
1308    
1309      Socket sock = new Socket("www.yahoo.com", 80);
1310      System.out.println("--- inputStreamToString('"+ sock + "') -----");
1311      InputStream sin = sock.getInputStream();
1312      OutputStream sout = sock.getOutputStream();
1313      sout.write("GET /index.html\n\n".getBytes()); 
1314      sout.flush();
1315      System.out.println(inputStreamToString(sin, true));
1316      
1317      Object obj = new Object();
1318      System.out.println("--- getClassResource('"+ obj.getClass() + ", 'String.class') -----");
1319      System.out.println(getClassResource(obj.getClass(), "String"));
1320    
1321      System.out.println("--- getClassResource('"+ IOUtil.class + ", 'Args.class') -----");
1322      System.out.println(inputStreamToString(getClassResource(IOUtil.class, "compile.sh")));
1323      
1324      copyFileTest();   
1325      } //~constructor
1326      
1327      void copyFileTest() throws IOException {
1328        File source = new File("/tmp/foo");
1329        File dest = new File("/tmp/bar");     
1330        System.out.println("Copy test: " + source + " to " + dest);
1331        copyDirectory(source, dest);
1332        }
1333    } //~Test
1334      
1335    }           //~ IOUtil  
1336    
1337    
1338      
1339      
1340