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
006package fc.io;  
007
008import fc.util.*;
009import java.io.*;
010import java.net.*;
011import java.nio.*;
012import java.nio.channels.*;
013import java.nio.charset.*;
014import java.util.*;
015import java.util.regex.*;
016import java.text.*;
017import java.security.*;
018
019/** Misc. IO utilities 
020
021@author   hursh jain
022@version  1.1 
023**/
024public final class IOUtil
025{
026//for internal class debugging at development time
027private static final boolean dbg = false;
028
029/** System.getProperty("file.separator") for convenience **/
030public static final String FILE_SEP = System.getProperty("file.separator");
031
032/** System.getProperty("path.separator") for convenience **/
033public static final String PATH_SEP = System.getProperty("path.separator");
034
035/** System.getProperty("line.separator") for convenience **/
036public static final String LINE_SEP = System.getProperty("line.separator");
037
038public static final int FILECOPY_OVERWRITE = 0;
039public static final int FILECOPY_NO_OVERWRITE = 1;
040
041/** 
042Ignores a directory copy command if the destination directory already
043exists.
044**/
045public static final int DIRCOPY_NO_OVERWRITE = 2;
046
047/** 
048Copies the existing directory and overwrites any files with
049the same name in the destination directory. Files/directories that 
050exist in the destination but not in the source directory are left
051untouched.
052**/
053public static final int DIRCOPY_ADD_OR_OVERWRITE = 3;
054
055/** 
056Copies the existing directory. If the destination directory already
057exists, the entire target directory is first deleted and then
058the specified directory is copied to the destination
059**/
060public static final int DIRCOPY_DELETE_AND_WRITE = 4;
061
062
063/** number of bytes contained in a kilobyte */
064public static final int ONE_KB = 1024;
065
066/** number of bytes contained in a megabyte. */
067public static final int ONE_MB = ONE_KB * ONE_KB;
068
069/** number of bytes contained in a gigabyte. */
070public static final int ONE_GB = ONE_KB * ONE_MB;
071
072/** number of bytes equal to 2^32 **/
073public static final long FOUR_GB = ONE_GB * 4L;
074
075/**
076Beeps by writing the beep control code to System.out
077*/
078public static void beep() 
079  {
080  System.out.print("\007");
081  System.out.flush();
082  }
083
084/**
085Copies source file (not directory) to destination. If the destination
086already exists, then no copy is made (that is, the destination is
087<b>not</b> overwritten and this method returns silently (an Exception
088is 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*/
094public static boolean copyFile(File source, File dest) throws FileNotFoundException, IOException
095  {
096  return copyFile(source, dest, IOUtil.FILECOPY_NO_OVERWRITE);
097  }
098
099/**
100Copies the source file (not directory) to the specified
101destination file or directory. If a directory is specified as
102the destination, then the source file is copied into that
103directory. To specify the action when a file with the same name
104already exists in the specified directory, use the appropriate
105{@link #FILECOPY_OVERWRITE} or {@link #FILECOPY_NO_OVERWRITE}
106flags.
107If a file is specified as the destination, then the source file
108is copied to that file. If the specified file exists already
109then specify the appropriate overwrite flag.  
110<p>
111Try to use absolute path names for files and directories. Relative
112path names can be relative either to user.dir or where the jvm was invoked
113or some platform/jvm dependent place, so don't rely on relative paths.  
114Copying, moving or working with files is tricky in java. Similar 
115behavior is not defined for all platforms. For example, in WindowsNT/java1.2, 
116aliases cannot be opened/resolved and hence cannot be copied via 
117FileInput/Output streams. This sort of thing is best left to
118JNI calls native code. 
119<p>
120This method returns <tt>true</tt> if the directory was copied
121successfully, <tt>false</tt> otherwise. (for example, <tt>false</tt>
122will be returned when the copy mode is not to overwrite but the
123target 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*/  
129public 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/**
164Calls {@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*/
171public static boolean copyDirectory(File source, File dest) throws FileNotFoundException, IOException
172  {
173  return copyDirectory(source, dest, IOUtil.DIRCOPY_ADD_OR_OVERWRITE);
174  }
175
176/**
177Copies the source directory and all it's contents to the specified 
178destination directory. A directory must be specified both for the source 
179and the destination. 
180<p>
181To handle cases where the destination directory already exists
182use the appropriate {@link #DIRCOPY_NO_OVERWRITE}
183{@link #DIRCOPY_DELETE_AND_WRITE} and
184{@link #DIRCOPY_ADD_OR_OVERWRITE} flags.
185<p>
186Try to use absolute path names for files and directories. Relative
187path names can be relative either to user.dir or where the jvm was invoked
188or 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 
190behavior is not defined for all platforms. For example, in WindowsNT/java1.2, 
191aliases cannot be opened/resolved and hence cannot be copied via 
192FileInput/Output streams. This sort of thing is best left to
193JNI calls to POSIX or to platform specific code. 
194<p>
195This method returns <tt>true</tt> if the directory was copied
196successfully, <tt>false</tt> otherwise. (for example, <tt>false</tt>
197will be returned when the copy mode is not to overwrite but the
198target 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*/  
204public static boolean copyDirectory(File source, File dest, int copyflag) 
205throws 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/** 
268Copies all data from the specific input stream to the specified output stream.
269Closes both streams after it is finished.
270
271@param  in  the InputStream
272@param  out the OutputStream
273**/
274public 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/** 
290Alphabetizes the specified list by the filename. Only the
291filename is considered and not the path name (if any). The 
292collection 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>
298Any/all of these can be contained in the specified list at the
299same 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
301list is sorted by the default {@link String#compareTo(String)} implementation 
302of <tt>String</tt>.
303
304@param  list  the list to be sorted
305**/
306public 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/**
325This method recursively removes a specified file or recursively 
326removes 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 
328directory is not empty. 
329<p>
330Internally, this method delegates to {@link File#delete}, so if
331{@link File#delete} follows sym links, then so will this method.
332Be 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**/
338public 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/**
371Gets the total size for a directory and all of it's contents. If
372the specified argument is a regular file, returns the size of that
373file itself.
374
375@param  dir   the target dir
376@return     the directory or file size in bytes
377**/
378public 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/** 
399Buffers and returns the specified InputStream, if it is not already buffered.
400Does 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*/
405public 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/** 
419Buffers and returns the specified OutputStream, if it is not already buffered.
420Does 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**/
426public 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  
439public 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    
452public 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  
461public 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
470public 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/**
484Convenience method to print the contents of a java.util.Property object
485to a String (using the default platform encoding).
486
487**/
488public 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
500private static String defaultEncoding = null;
501
502/**
503Returns the default encoding used by the current platform. Returns
504<tt>null</tt> is the default encoding cannot be determined.
505**/
506public 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/**
526Returns the contents of an entire file as a List of individual lines. If the specified
527file does not exist or have no content return an empty List. Uses UTF-8 encoding to read the file.
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*/
534public 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/**
568Returns the contents of an entire file as a List of individual lines. If the specified
569file 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*/
576public 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/**
582Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
583and 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*/
587public static List fileToLines(File file) throws IOException
588  {
589  return fileToLines(file, true, "#|//");
590  }
591
592/**
593Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
594and 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*/
598public static List fileToLines(String filename) throws IOException
599  {
600  return fileToLines(new File(filename));
601  }
602  
603/**
604Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
605and lines beginning with the following characters are ignored: <tt>#</tt> and <tt>//</tt>
606
607@param  in  the input stream to be read
608*/
609public static List fileToLines(InputStream in) throws IOException
610  {
611  return fileToLines(in, true, "#|//");
612  } 
613  
614/** 
615Returns the contents of an entire file as a String. If the specified
616file does not exist returns <tt>null</tt>. Files that exist but have no
617content return an empty String.
618<p>
619<b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
620less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
621an <tt>IOException</tt> will be thrown.
622<br>
623<b>Note 2:</b> The files is converted into a String using an encoding
624that is determined programmatically (from the filesystem). This may
625not be totally reliable but there is no way around this because JDK 1.4
626provides <b>no</b> way to easily get the default platform encoding.
627Uses the <tt>ISO_8859_1</tt> encoding as a fallback measure, if the
628default 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**/
633public 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/** 
661Returns the contents of an entire file as a String. If the specified
662file does not exist returns <tt>null</tt>. Files that exist but have no
663content return an empty String.
664<p>
665<b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
666less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
667an <tt>IOException</tt> will be thrown.
668<br>
669<b>Note 2:</b> The files is converted into a String using an encoding
670that is determined programmatically (from the filesystem). This may
671not be totally reliable but there is no way around this because JDK 1.4
672provides <b>no</b> way to easily get the default platform encoding.
673Uses the <tt>ISO_8859_1</tt> encoding as a fallback measure, if the
674default 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**/
679public static String fileToString(String filename, boolean trim) throws IOException
680  {
681  return fileToString(new File(filename), trim);
682  }
683
684/** 
685Calls {@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**/
690public static String fileToString(String filename) throws IOException
691    {
692    return fileToString(filename, false);
693  }
694
695/** 
696Calls {@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**/
701public static String fileToString(File file) throws IOException
702    {
703    return fileToString(file, false);
704  }
705
706
707/**  
708Returns the contents of an entire file as a <tt>byte[]</tt>. If the specified
709file does not exist returns <tt>null</tt>. 
710<p>
711<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
712this method is limited to files less than or equal to 2^32 bytes. If 
713the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
714will 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**/
719public static byte[] fileToByteArray(String filename) throws IOException
720  {
721  return fileToByteArray(new File(filename));
722  }
723
724/**  
725Returns the contents of an entire file as a <tt>byte[]</tt>. If the specified
726file does not exist returns <tt>null</tt>. 
727<p>
728<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
729this method is limited to files less than or equal to 2^32 bytes. If 
730the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
731will be thrown.
732
733@param    file    the file to be read
734@return   byte[]    contains the bytes for that file
735**/
736public 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/**  
783Returns the contents of an entire file as a ByteBuffer backed by mapping the
784file to memory. If the specified file does not exist returns <tt>null</tt>.
785Mapped 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
787java.nio.MappedByteBuffer} documentation about concurrent modification or
788deletion of files that are mapped into memory.
789<p>
790The ByteBuffer returned by this method will have <tt>{@link
791ByteBuffer#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
794ByteBuffers cannot be greater than this size. If the specified file is greater 
795than 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**/
800public 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/**  
834Returns the contents of an entire file as a <tt>char[]</tt>. If the specified
835file does not exist returns <tt>null</tt>. 
836<p>
837<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
838this method is limited to files less than or equal to 2^32 bytes. If 
839the specified file is greater than 2^32 bytes, an <tt>IOException</tt> 
840will 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**/
847public static char[] fileToCharArray(File file, String encoding) 
848throws 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
871  int fsize = (int) longfsize;
872  char[] buf =  readerToCharArray(reader, (int)fsize);  //buf_fize (fsize) can be greater than chars read
873  
874  /*
875  Can't do it this way - we create a buf of file size bytes but the number of characters
876  read can be less than file size (because of various encodings). This way returns a buf
877  with NULL characters at the end, so either have to trim the buf or just use a char array
878  writer
879  */
880  /*
881  buf = new char[fsize];
882  try {
883    int read, pos = 0;
884    while (pos < fsize) 
885      {
886      //Usually, this will read everything the first time 
887      read = reader.read(buf, pos, fsize - pos);
888      pos += read;
889      if (read < 0) { //EOF
890        break;
891        }
892      }
893  
894    if (dbg && pos != fsize) {
895      System.out.println("File: " +  file.getAbsolutePath() 
896        + " has bytes [" + fsize + "], read [" + pos + "] chars\n"
897        + "This is expected since byte->char will loose characters for non-ascii files. Currently using [" 
898        + encoding + "] to read this file"
899        );
900      }
901    }
902    
903    //at this point buf will have pos characters, not fsize characters.
904  
905  finally {
906    fin.close(); 
907    }
908  */
909  
910  return buf;
911  }
912
913/*
914Reads the entire Stream and returns all read data as a 
915<tt>char[]</tt>. If no data is available, returns an empty char[].
916*/
917public static char[] readerToCharArray(Reader reader) 
918throws IOException
919  {
920  Argcheck.notnull(reader);
921
922  return readerToCharArray(reader, 1024);   
923  }
924
925
926/*
927Reads the entire Stream and returns all read data as a 
928<tt>char[]</tt>. If no data is available, returns an empty char[].
929*/
930public static char[] readerToCharArray(Reader reader, int buffer_size) 
931throws IOException
932  {
933  Argcheck.notnull(reader);
934
935  char[] buf = new char[buffer_size];
936  if (dbg) System.out.println("readerToCharArray(), block=yes");
937
938  final CharArrayWriter cout = new CharArrayWriter(buffer_size);
939  int read = 0;
940  while (true) {
941    read = reader.read(buf, 0, buffer_size);  
942    if (read == -1)
943      break;  
944    cout.write(buf, 0, read);
945    }
946
947  return cout.toCharArray();    
948  }
949
950
951
952
953/** 
954Converts the specified byte array into a CharBuffer using the specified
955encoding. The returned CharBuffer can be directly used in statements such
956as <tt>System.out.println</tt> to print it's contents,
957<p>
958This method returns <tt>null</tt> if the specified array is <tt>null</tt>
959or if the specified encoding is <tt>null</tt>.
960
961@param  array   the array to convert
962@param  encoding  the {@link java.nio.charset.Charset charset} encoding to use to     
963          convert bytes into chars
964**/
965public static CharBuffer arrayToCharBuffer(byte[] array, String encoding) 
966  {
967  if ( (array == null) || (encoding == null))
968    return null;  
969    
970  Charset cset = Charset.forName(encoding);
971  CharBuffer cbuf = cset.decode(ByteBuffer.wrap(array));
972  return cbuf;
973  }
974
975/** 
976Convenience method that delegates to {@link #arrayToCharBuffer(byte[],
977String)} using UTF-8 encoding by default.
978**/ 
979public static CharBuffer arrayToCharBuffer(byte[] array) 
980  {
981  //String enc = IOUtil.getDefaultEncoding();
982  //enc = (enc != null) ? enc : "ISO-8859-1";
983  //UTF-8 is safer overall
984  return arrayToCharBuffer(array, "UTF-8");
985  } 
986
987/**
988Reads the entire InputStream and returns all read data as a 
989<tt>byte[]</tt>. If no data is available, returns <tt>null</tt>.
990
991@param  in    the InputStream to read
992@param  block   if <tt>true</tt>, this method will block until all 
993        available data from the specified input stream
994        has been read. If <tt>false</tt>, this method will
995        read and return as much data as currently is available 
996        is read without blocking. The available amount is 
997        that returned by the available() method of the specified 
998        input stream.
999
1000@throws NegativeArraySizeException  if the specified input stream returns
1001                  a negative number for available()       
1002**/
1003public static byte[] inputStreamToByteArray(InputStream in, boolean block) 
1004throws IOException
1005  {
1006  Argcheck.notnull(in, "InputStream was null");
1007
1008  final BufferedInputStream bin = bufferStream(in);
1009  
1010  if (! block) {
1011    int buffer_size = bin.available();
1012    if (dbg) System.out.println("inputStreamToByteArray(), block=no, buffersize=" + buffer_size);
1013    byte[] buf = new byte[buffer_size];
1014    int read = 0;
1015    int pos = 0;
1016    while (read < buffer_size) {
1017      read += bin.read(buf, pos, buffer_size - read);
1018      pos = read + 1;
1019      }
1020    if (dbg) System.out.println("inputStreamToByteArray(), returning buf=" + buf.length + " bytes");
1021    return buf;
1022    }
1023  
1024  //block
1025  int buffer_size = 1024;
1026  byte[] buf = new byte[buffer_size];
1027  if (dbg) System.out.println("inputStreamToByteArray(), block=yes");
1028
1029  final ByteArrayOutputStream bout = new ByteArrayOutputStream(buffer_size);
1030  int read = 0;
1031  while (true) {
1032    read = bin.read(buf, 0, buffer_size); 
1033    if (read == -1)
1034      break;  
1035    bout.write(buf, 0, read);
1036    }
1037  //if size() is 0, toByteArray returns an array of size 0. we
1038  //return null instead.
1039  if (bout.size() == 0) {
1040    return null;
1041    }
1042  return bout.toByteArray();    
1043  }
1044
1045/**
1046Calls inputStreamToByteArray(in, <tt>true</tt>)
1047*/
1048public static byte[] inputStreamToByteArray(InputStream in) throws IOException
1049  {
1050  return inputStreamToByteArray(in, true);  
1051  }
1052
1053
1054/**
1055Reads the entire InputStream and returns all read data as a <tt>String</tt>, using UTF-8
1056encoding. If no data is available, returns <tt>null</tt>. The specified input stream is
1057<u>not</u> closed.
1058
1059@param  in    the InputStream to read
1060@param  block   if <tt>true</tt>, this method will block until all 
1061        available data from the specified input stream
1062        has been read. If <tt>false</tt>, this method will
1063        read and return as much data as currently is available 
1064        is read without blocking. The available amount is 
1065        that returned by the specified input stream.
1066
1067@throws NegativeArraySizeException  if the specified input stream returns
1068                  a negative number for available()
1069**/
1070public static String inputStreamToString(InputStream in, boolean block) 
1071throws IOException
1072  {
1073  final byte[] buf = inputStreamToByteArray(in, block);
1074  if (buf == null) {  
1075    return null;
1076    }
1077  return new String(buf, "UTF-8");    
1078  }   //~inputStreamToString
1079
1080
1081
1082/**
1083Reads the entire InputStream and returns all read data as a 
1084<tt>String</tt> (using the specified platform encoding). If
1085no data is available, returns <tt>null</tt>. The specified input
1086stream is <u>not</u> closed.
1087<p>
1088This method will block until all available data from the specified 
1089input stream has been read.
1090
1091@param  in      the input stream to read
1092@param  encoding  the {@link java.nio.charset.Charset} encoding name to use to
1093          convert bytes into chars
1094*/
1095public static String inputStreamToString(InputStream in, String encoding) 
1096throws IOException
1097  {
1098  final byte[] buf = inputStreamToByteArray(in, true);
1099  if (buf == null) {  
1100    return null;
1101    }
1102  return new String(buf, encoding);
1103  }
1104
1105
1106/**
1107Calls inputStreamToString(in, <tt>true</tt>)
1108*/
1109public static String inputStreamToString(InputStream in) throws IOException
1110  {
1111  return inputStreamToString(in, true);
1112  }
1113
1114
1115/**
1116Convenience method to print the stack trace of an Exception (or Throwable)
1117to a String (using the default platform encoding). (The <tt>getMessage()</tt>
1118method of a Throwable does not print the entire stack trace).
1119**/
1120public static String throwableToString(final Throwable e)
1121  {
1122  Argcheck.notnull(e, "The specified exception object was null");
1123  String temp = null;
1124  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1125  final PrintStream pout = new PrintStream(bout);
1126  e.printStackTrace(pout);
1127  pout.flush();
1128  temp = bout.toString();
1129  pout.close();
1130  return temp;
1131  }
1132
1133/**
1134Convenience method that returns the current execution stack trace as a String.
1135(using the default platform encoding).
1136**/
1137public static String stackTrace()
1138  {
1139  String temp = null;
1140  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1141  final PrintStream pout = new PrintStream(bout);
1142  pout.println("==================== Debug Stack Trace ======================");
1143  new Exception().printStackTrace(pout);
1144  pout.println("=============================================================");
1145  pout.flush();
1146  temp = bout.toString();
1147  pout.close();
1148  return temp;
1149  }
1150
1151/** 
1152Calls {@link #fileSizeToString(long)} and truncates the 
1153size to fit in the specified number of digits. For example,
1154a file size of <tt>4.455 KB</tt> and a length of 2 will 
1155return <tt>4.45 KB</tt>
1156
1157@param  filesize  the size of the file in bytes
1158@param  length    the max number of digits <b>after</b>
1159          the decimal point
1160*/
1161public static String fileSizeToString(long filesize, int length)
1162  {
1163  NumberFormat nf = NumberFormat.getInstance(); 
1164  nf.setMaximumFractionDigits(length);
1165  return fileSizeToStringImpl(nf, filesize);
1166  }
1167
1168
1169/** 
1170Converts the specified file size into a human readable description. 
1171Similar to the <tt>"--human-readable"</tt> flag found in various 
1172GNU programs.
1173
1174@param  filesize  the size of the file in bytes
1175*/
1176public static String fileSizeToString(long filesize)
1177  {
1178  NumberFormat nf = NumberFormat.getInstance(); 
1179  return fileSizeToStringImpl(nf, filesize);    
1180  }
1181  
1182private static final String fileSizeToStringImpl(
1183          NumberFormat nf, long filesize)
1184  {
1185  StringBuffer buf = new StringBuffer(32);
1186  if (filesize > ONE_GB) 
1187        buf.append(nf.format(filesize / ONE_GB)).append(" GB");
1188  else if (filesize > ONE_MB) 
1189    buf.append(nf.format( filesize / ONE_MB)).append(" MB");
1190  else if (filesize > ONE_KB)
1191    buf.append(nf.format(filesize / ONE_KB)).append(" KB");
1192  else    
1193    buf.append(nf.format(filesize)).append(" bytes");
1194  
1195  return buf.toString();
1196  }
1197
1198/**
1199Converts a string file size into a number denoting the equivalent bytes. 
1200For example:
1201<pre>
120234K, 34KB --> 34 bytes
120334M, 34megabytes --> 34 * 1024 bytes
1204</pre>
1205Allows numbers to end with <tt>k..., m..., g...., b....</tt> or no suffix 
1206at all. Suffixes are case insensitive. 
1207*/
1208public static long stringToFileSize(String str)
1209  {
1210  Argcheck.notnull(str, "the specified string was null");
1211  
1212  str = str.replace(" ",""); //remove all leading, trailing, embedded spaces
1213  
1214  int pos = 0;  //this is slighty easier than str.indexOf
1215  for (int n = 0; n < str.length(); n++) 
1216    {
1217    char c = str.charAt(n);
1218    switch (c) {
1219      case 'g': case 'G':
1220        return Long.parseLong(str.substring(0,n)) * ONE_GB; 
1221      case 'm': case 'M':
1222        return Long.parseLong(str.substring(0,n)) * ONE_MB; 
1223      case 'k': case 'K':
1224        return Long.parseLong(str.substring(0,n)) * ONE_KB; 
1225      case 'b': case 'B':
1226        return Long.parseLong(str.substring(0,n)); 
1227      default: 
1228        //nothing to do
1229      }
1230    }
1231  return Long.parseLong(str);
1232  }
1233
1234/**
1235Returns the SHA-1 hash of the specified byte buffer.
1236<p>
1237This method is <b>not</b> thread safe (as far as I can tell, since it uses
1238<code>MessageDigest.getInstance</code>, but <i>if</i> that is thread safe, then
1239this method is thread safe). The invoker should invoke it in a thread safe way.
1240*/
1241public static final String sha1hash(byte[] buf) throws NoSuchAlgorithmException
1242  {
1243  MessageDigest md = MessageDigest.getInstance("SHA-1");
1244  byte[] digest = md.digest(buf);
1245  StringBuilder sb = new StringBuilder();
1246  for (byte b: digest)
1247    {
1248    String hex = Integer.toHexString((int)0xff & b);
1249    if (hex.length()==1) sb.append("0");
1250    sb.append(hex);
1251    }
1252
1253  return sb.toString();
1254  }
1255
1256
1257/** 
1258 Get the InputStream for the specified resource (in the same directory as from where the specified class was loaded). 
1259 Returns null if not found. 
1260 */
1261public static InputStream getClassResource(Class clazz, String resource_name) throws IOException
1262  {
1263  if (clazz == null) {
1264    return null;
1265    }
1266  
1267  if (resource_name.startsWith("/")) {
1268    resource_name = resource_name.substring(1,resource_name.length());
1269    }
1270  InputStream in = clazz.getResourceAsStream(resource_name);
1271  return in;
1272    }
1273  
1274/**
1275Usage:
1276java IOUtil args where args are:
1277  -file full-path-to-file 
1278      [for fileToString and other tests]
1279  
1280**/
1281public static void main(String[] args) throws Exception
1282  {
1283  Args myargs = new Args(args);
1284  String fts = myargs.getRequired("file");
1285  new Test(fts);
1286  }
1287
1288
1289/**
1290Unit Test History   
1291<pre>
1292Class Version Tester  Status      Notes
12931.1       hj    still-testing limited testing only, more needs to be done
1294</pre>
1295*/
1296static private class Test {
1297Test(String fileToString) throws Exception
1298  {
1299  java.io.File sourcefile = new java.io.File("testsourcefile");
1300  java.io.File sourcedir = new java.io.File("testsourcedir"); 
1301  java.io.File destfile = new java.io.File("testdestfile"); 
1302  java.io.File destdir = new java.io.File("testdestdir"); 
1303
1304  String dir1 = "foo";
1305  String dir2 = "foo" + FILE_SEP;     
1306  String f = "f1";
1307  //String r1 = IOUtil.makeFilePath(dir1,f);
1308  //String r2 = IOUtil.makeFilePath(dir2,f);
1309  for(long n = 2; n < 4000000001L; n *= 1000) {
1310    System.out.println("file size " + n + " = " 
1311              + IOUtil.fileSizeToString(n));
1312    }
1313  
1314  System.out.println("Platform encoding, via file.encoding: " + System.getProperty("file.encoding"));
1315  System.out.println("Platform encoding: " + IOUtil.getDefaultEncoding());
1316  System.out.println("--- fileToString('"+ fileToString + "') -----");
1317  String filestr = fileToString(fileToString);
1318  System.out.println("file '" + fileToString + "' read into String"); 
1319  System.out.println("String length = " + filestr.length());
1320  System.out.println("String data = ");
1321  System.out.println(filestr);
1322
1323  List linelist = fileToLines(new File(fileToString));
1324  System.out.println("--- fileToList('"+ fileToString + "') -----");
1325  for (int n = 0; n < linelist.size(); n++) {
1326    System.out.println("[line:" + n + "]: " + linelist.get(n));
1327    }
1328
1329  FileInputStream fin = new FileInputStream(fileToString);
1330  System.out.println("--- inputStreamToString('"+ fileToString + "') -----");
1331  String str = inputStreamToString(fin, false);
1332  System.out.println(str);
1333  System.out.println(fileToString + ", size=" + str.length());
1334
1335  Socket sock = new Socket("www.yahoo.com", 80);
1336  System.out.println("--- inputStreamToString('"+ sock + "') -----");
1337  InputStream sin = sock.getInputStream();
1338  OutputStream sout = sock.getOutputStream();
1339  sout.write("GET /index.html\n\n".getBytes()); 
1340  sout.flush();
1341  System.out.println(inputStreamToString(sin, true));
1342  
1343  Object obj = new Object();
1344  System.out.println("--- getClassResource('"+ obj.getClass() + ", 'String.class') -----");
1345  System.out.println(getClassResource(obj.getClass(), "String"));
1346
1347  System.out.println("--- getClassResource('"+ IOUtil.class + ", 'Args.class') -----");
1348  System.out.println(inputStreamToString(getClassResource(IOUtil.class, "compile.sh")));
1349  
1350  copyFileTest();   
1351  } //~constructor
1352  
1353  void copyFileTest() throws IOException {
1354    File source = new File("/tmp/foo");
1355    File dest = new File("/tmp/bar");     
1356    System.out.println("Copy test: " + source + " to " + dest);
1357    copyDirectory(source, dest);
1358    }
1359} //~Test
1360  
1361}           //~ IOUtil  
1362
1363
1364  
1365  
1366