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.
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  int fsize = (int) longfsize;
871  char[]  buf = new char[fsize];
872
873  try {
874    int read, pos = 0;
875    while (pos < fsize) 
876      {
877      /* Usually, this will read everything the first time */
878      read = reader.read(buf, pos, fsize - pos);
879      pos += read;
880      if (read < 0) { //EOF
881        break;
882        }
883      }
884
885    if (dbg) System.out.println("Read file char[] = " + buf.length + " count");
886    
887    if (dbg && pos != fsize) {
888      System.out.println("File: " +  file.getAbsolutePath() 
889        + " has bytes [" + fsize + "], read [" + pos + "] chars\n"
890        + "This is expected since byte->char will loose characters for non-ascii files. Currently using [" 
891        + encoding + "] to read this file"
892        );
893      }
894    }
895  finally {
896    fin.close(); 
897    }
898
899  return buf;
900  }
901
902/*
903Reads the entire Stream and returns all read data as a 
904<tt>char[]</tt>. If no data is available, returns an empty char[].
905*/
906public static char[] readerToCharArray(Reader reader) 
907throws IOException
908  {
909  Argcheck.notnull(reader);
910
911  int buffer_size = 1024;
912  char[] buf = new char[buffer_size];
913  if (dbg) System.out.println("readerToCharArray(), block=yes");
914
915  final CharArrayWriter cout = new CharArrayWriter(buffer_size);
916  int read = 0;
917  while (true) {
918    read = reader.read(buf, 0, buffer_size);  
919    if (read == -1)
920      break;  
921    cout.write(buf, 0, read);
922    }
923
924  return cout.toCharArray();    
925  }
926
927
928/** 
929Converts the specified byte array into a CharBuffer using the specified
930encoding. The returned CharBuffer can be directly used in statements such
931as <tt>System.out.println</tt> to print it's contents,
932<p>
933This method returns <tt>null</tt> if the specified array is <tt>null</tt>
934or if the specified encoding is <tt>null</tt>.
935
936@param  array   the array to convert
937@param  encoding  the {@link java.nio.charset.Charset charset} encoding to use to     
938          convert bytes into chars
939**/
940public static CharBuffer arrayToCharBuffer(byte[] array, String encoding) 
941  {
942  if ( (array == null) || (encoding == null))
943    return null;  
944    
945  Charset cset = Charset.forName(encoding);
946  CharBuffer cbuf = cset.decode(ByteBuffer.wrap(array));
947  return cbuf;
948  }
949
950/** 
951Convenience method that delegates to {@link #arrayToCharBuffer(byte[],
952String)} using UTF-8 encoding by default.
953**/ 
954public static CharBuffer arrayToCharBuffer(byte[] array) 
955  {
956  //String enc = IOUtil.getDefaultEncoding();
957  //enc = (enc != null) ? enc : "ISO-8859-1";
958  //UTF-8 is safer overall
959  return arrayToCharBuffer(array, "UTF-8");
960  } 
961
962/**
963Reads the entire InputStream and returns all read data as a 
964<tt>byte[]</tt>. If no data is available, returns <tt>null</tt>.
965
966@param  in    the InputStream to read
967@param  block   if <tt>true</tt>, this method will block until all 
968        available data from the specified input stream
969        has been read. If <tt>false</tt>, this method will
970        read and return as much data as currently is available 
971        is read without blocking. The available amount is 
972        that returned by the available() method of the specified 
973        input stream.
974
975@throws NegativeArraySizeException  if the specified input stream returns
976                  a negative number for available()       
977**/
978public static byte[] inputStreamToByteArray(InputStream in, boolean block) 
979throws IOException
980  {
981  Argcheck.notnull(in, "InputStream was null");
982
983  final BufferedInputStream bin = bufferStream(in);
984  
985  if (! block) {
986    int buffer_size = bin.available();
987    if (dbg) System.out.println("inputStreamToByteArray(), block=no, buffersize=" + buffer_size);
988    byte[] buf = new byte[buffer_size];
989    int read = 0;
990    int pos = 0;
991    while (read < buffer_size) {
992      read += bin.read(buf, pos, buffer_size - read);
993      pos = read + 1;
994      }
995    if (dbg) System.out.println("inputStreamToByteArray(), returning buf=" + buf.length + " bytes");
996    return buf;
997    }
998  
999  //block
1000  int buffer_size = 1024;
1001  byte[] buf = new byte[buffer_size];
1002  if (dbg) System.out.println("inputStreamToByteArray(), block=yes");
1003
1004  final ByteArrayOutputStream bout = new ByteArrayOutputStream(buffer_size);
1005  int read = 0;
1006  while (true) {
1007    read = bin.read(buf, 0, buffer_size); 
1008    if (read == -1)
1009      break;  
1010    bout.write(buf, 0, read);
1011    }
1012  //if size() is 0, toByteArray returns an array of size 0. we
1013  //return null instead.
1014  if (bout.size() == 0) {
1015    return null;
1016    }
1017  return bout.toByteArray();    
1018  }
1019
1020/**
1021Calls inputStreamToByteArray(in, <tt>true</tt>)
1022*/
1023public static byte[] inputStreamToByteArray(InputStream in) throws IOException
1024  {
1025  return inputStreamToByteArray(in, true);  
1026  }
1027
1028
1029/**
1030Reads the entire InputStream and returns all read data as a 
1031<tt>String</tt> (using the default platform encoding). If
1032no data is available, returns <tt>null</tt>. The specified input
1033stream is <u>not</u> closed.
1034
1035@param  in    the InputStream to read
1036@param  block   if <tt>true</tt>, this method will block until all 
1037        available data from the specified input stream
1038        has been read. If <tt>false</tt>, this method will
1039        read and return as much data as currently is available 
1040        is read without blocking. The available amount is 
1041        that returned by the specified input stream.
1042
1043@throws NegativeArraySizeException  if the specified input stream returns
1044                  a negative number for available()
1045**/
1046public static String inputStreamToString(InputStream in, boolean block) 
1047throws IOException
1048  {
1049  final byte[] buf = inputStreamToByteArray(in, block);
1050  if (buf == null)  
1051    return null;
1052  return new String(buf);   
1053  }   //~inputStreamToString
1054
1055
1056
1057/**
1058Reads the entire InputStream and returns all read data as a 
1059<tt>String</tt> (using the specified platform encoding). If
1060no data is available, returns <tt>null</tt>. The specified input
1061stream is <u>not</u> closed.
1062<p>
1063This method will block until all available data from the specified 
1064input stream has been read.
1065
1066@param  in      the input stream to read
1067@param  encoding  the {@link java.nio.charset.Charset} encoding name to use to
1068          convert bytes into chars
1069*/
1070public static String inputStreamToString(InputStream in, String encoding) 
1071throws IOException
1072  {
1073  final byte[] buf = inputStreamToByteArray(in, true);
1074  if (buf == null)  
1075    return null;
1076  return new String(buf, encoding);
1077  }
1078
1079
1080/**
1081Calls inputStreamToString(in, <tt>true</tt>)
1082*/
1083public static String inputStreamToString(InputStream in) throws IOException
1084  {
1085  return inputStreamToString(in, true);
1086  }
1087
1088
1089/**
1090Convenience method to print the stack trace of an Exception (or Throwable)
1091to a String (using the default platform encoding). (The <tt>getMessage()</tt>
1092method of a Throwable does not print the entire stack trace).
1093**/
1094public static String throwableToString(final Throwable e)
1095  {
1096  Argcheck.notnull(e, "The specified exception object was null");
1097  String temp = null;
1098  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1099  final PrintStream pout = new PrintStream(bout);
1100  e.printStackTrace(pout);
1101  pout.flush();
1102  temp = bout.toString();
1103  pout.close();
1104  return temp;
1105  }
1106
1107/**
1108Convenience method that returns the current execution stack trace as a String.
1109(using the default platform encoding).
1110**/
1111public static String stackTrace()
1112  {
1113  String temp = null;
1114  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1115  final PrintStream pout = new PrintStream(bout);
1116  pout.println("==================== Debug Stack Trace ======================");
1117  new Exception().printStackTrace(pout);
1118  pout.println("=============================================================");
1119  pout.flush();
1120  temp = bout.toString();
1121  pout.close();
1122  return temp;
1123  }
1124
1125/** 
1126Calls {@link #fileSizeToString(long)} and truncates the 
1127size to fit in the specified number of digits. For example,
1128a file size of <tt>4.455 KB</tt> and a length of 2 will 
1129return <tt>4.45 KB</tt>
1130
1131@param  filesize  the size of the file in bytes
1132@param  length    the max number of digits <b>after</b>
1133          the decimal point
1134*/
1135public static String fileSizeToString(long filesize, int length)
1136  {
1137  NumberFormat nf = NumberFormat.getInstance(); 
1138  nf.setMaximumFractionDigits(length);
1139  return fileSizeToStringImpl(nf, filesize);
1140  }
1141
1142
1143/** 
1144Converts the specified file size into a human readable description. 
1145Similar to the <tt>"--human-readable"</tt> flag found in various 
1146GNU programs.
1147
1148@param  filesize  the size of the file in bytes
1149*/
1150public static String fileSizeToString(long filesize)
1151  {
1152  NumberFormat nf = NumberFormat.getInstance(); 
1153  return fileSizeToStringImpl(nf, filesize);    
1154  }
1155  
1156private static final String fileSizeToStringImpl(
1157          NumberFormat nf, long filesize)
1158  {
1159  StringBuffer buf = new StringBuffer(32);
1160  if (filesize > ONE_GB) 
1161        buf.append(nf.format(filesize / ONE_GB)).append(" GB");
1162  else if (filesize > ONE_MB) 
1163    buf.append(nf.format( filesize / ONE_MB)).append(" MB");
1164  else if (filesize > ONE_KB)
1165    buf.append(nf.format(filesize / ONE_KB)).append(" KB");
1166  else    
1167    buf.append(nf.format(filesize)).append(" bytes");
1168  
1169  return buf.toString();
1170  }
1171
1172/**
1173Converts a string file size into a number denoting the equivalent bytes. 
1174For example:
1175<pre>
117634K, 34KB --> 34 bytes
117734M, 34megabytes --> 34 * 1024 bytes
1178</pre>
1179Allows numbers to end with <tt>k..., m..., g...., b....</tt> or no suffix 
1180at all. Suffixes are case insensitive. 
1181*/
1182public static long stringToFileSize(String str)
1183  {
1184  Argcheck.notnull(str, "the specified string was null");
1185  
1186  str = str.replace(" ",""); //remove all leading, trailing, embedded spaces
1187  
1188  int pos = 0;  //this is slighty easier than str.indexOf
1189  for (int n = 0; n < str.length(); n++) 
1190    {
1191    char c = str.charAt(n);
1192    switch (c) {
1193      case 'g': case 'G':
1194        return Long.parseLong(str.substring(0,n)) * ONE_GB; 
1195      case 'm': case 'M':
1196        return Long.parseLong(str.substring(0,n)) * ONE_MB; 
1197      case 'k': case 'K':
1198        return Long.parseLong(str.substring(0,n)) * ONE_KB; 
1199      case 'b': case 'B':
1200        return Long.parseLong(str.substring(0,n)); 
1201      default: 
1202        //nothing to do
1203      }
1204    }
1205  return Long.parseLong(str);
1206  }
1207
1208/**
1209Returns the SHA-1 hash of the specified byte buffer.
1210<p>
1211This method is <b>not</b> thread safe (as far as I can tell, since it uses
1212<code>MessageDigest.getInstance</code>, but <i>if</i> that is thread safe, then
1213this method is thread safe). The invoker should invoke it in a thread safe way.
1214*/
1215public static final String sha1hash(byte[] buf) throws NoSuchAlgorithmException
1216  {
1217  MessageDigest md = MessageDigest.getInstance("SHA-1");
1218  byte[] digest = md.digest(buf);
1219  StringBuilder sb = new StringBuilder();
1220  for (byte b: digest)
1221    {
1222    String hex = Integer.toHexString((int)0xff & b);
1223    if (hex.length()==1) sb.append("0");
1224    sb.append(hex);
1225    }
1226
1227  return sb.toString();
1228  }
1229
1230
1231/** 
1232 Get the InputStream for the specified resource (in the same directory as from where the specified class was loaded). 
1233 Returns null if not found. 
1234 */
1235public static InputStream getClassResource(Class clazz, String resource_name) throws IOException
1236  {
1237  if (clazz == null) {
1238    return null;
1239    }
1240  
1241  if (resource_name.startsWith("/")) {
1242    resource_name = resource_name.substring(1,resource_name.length());
1243    }
1244  InputStream in = clazz.getResourceAsStream(resource_name);
1245  return in;
1246    }
1247  
1248/**
1249Usage:
1250java IOUtil args where args are:
1251  -file full-path-to-file 
1252      [for fileToString and other tests]
1253  
1254**/
1255public static void main(String[] args) throws Exception
1256  {
1257  Args myargs = new Args(args);
1258  String fts = myargs.getRequired("file");
1259  new Test(fts);
1260  }
1261
1262
1263/**
1264Unit Test History   
1265<pre>
1266Class Version Tester  Status      Notes
12671.1       hj    still-testing limited testing only, more needs to be done
1268</pre>
1269*/
1270static private class Test {
1271Test(String fileToString) throws Exception
1272  {
1273  java.io.File sourcefile = new java.io.File("testsourcefile");
1274  java.io.File sourcedir = new java.io.File("testsourcedir"); 
1275  java.io.File destfile = new java.io.File("testdestfile"); 
1276  java.io.File destdir = new java.io.File("testdestdir"); 
1277
1278  String dir1 = "foo";
1279  String dir2 = "foo" + FILE_SEP;     
1280  String f = "f1";
1281  //String r1 = IOUtil.makeFilePath(dir1,f);
1282  //String r2 = IOUtil.makeFilePath(dir2,f);
1283  for(long n = 2; n < 4000000001L; n *= 1000) {
1284    System.out.println("file size " + n + " = " 
1285              + IOUtil.fileSizeToString(n));
1286    }
1287  
1288  System.out.println("Platform encoding, via file.encoding: " + System.getProperty("file.encoding"));
1289  System.out.println("Platform encoding: " + IOUtil.getDefaultEncoding());
1290  System.out.println("--- fileToString('"+ fileToString + "') -----");
1291  String filestr = fileToString(fileToString);
1292  System.out.println("file '" + fileToString + "' read into String"); 
1293  System.out.println("String length = " + filestr.length());
1294  System.out.println("String data = ");
1295  System.out.println(filestr);
1296
1297  List linelist = fileToLines(new File(fileToString));
1298  System.out.println("--- fileToList('"+ fileToString + "') -----");
1299  for (int n = 0; n < linelist.size(); n++) {
1300    System.out.println("[line:" + n + "]: " + linelist.get(n));
1301    }
1302
1303  FileInputStream fin = new FileInputStream(fileToString);
1304  System.out.println("--- inputStreamToString('"+ fileToString + "') -----");
1305  String str = inputStreamToString(fin, false);
1306  System.out.println(str);
1307  System.out.println(fileToString + ", size=" + str.length());
1308
1309  Socket sock = new Socket("www.yahoo.com", 80);
1310  System.out.println("--- inputStreamToString('"+ sock + "') -----");
1311  InputStream sin = sock.getInputStream();
1312  OutputStream sout = sock.getOutputStream();
1313  sout.write("GET /index.html\n\n".getBytes()); 
1314  sout.flush();
1315  System.out.println(inputStreamToString(sin, true));
1316  
1317  Object obj = new Object();
1318  System.out.println("--- getClassResource('"+ obj.getClass() + ", 'String.class') -----");
1319  System.out.println(getClassResource(obj.getClass(), "String"));
1320
1321  System.out.println("--- getClassResource('"+ IOUtil.class + ", 'Args.class') -----");
1322  System.out.println(inputStreamToString(getClassResource(IOUtil.class, "compile.sh")));
1323  
1324  copyFileTest();   
1325  } //~constructor
1326  
1327  void copyFileTest() throws IOException {
1328    File source = new File("/tmp/foo");
1329    File dest = new File("/tmp/bar");     
1330    System.out.println("Copy test: " + source + " to " + dest);
1331    copyDirectory(source, dest);
1332    }
1333} //~Test
1334  
1335}           //~ IOUtil  
1336
1337
1338  
1339  
1340