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