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 source   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 <code>true</code> if the directory was copied
121successfully, <code>false</code> otherwise. (for example, <code>false</code>
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 source   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 <code>true</code> if the directory was copied
196successfully, <code>false</code> otherwise. (for example, <code>false</code>
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
289public static void saveStreamToFile(InputStream in, File f) 
290                          throws IOException
291  {
292  copyStream(in, new FileOutputStream(f));
293  } 
294
295
296/** 
297Alphabetizes the specified list by the filename. Only the
298filename is considered and not the path name (if any). The 
299collection should contain one of: 
300<ul>
301  <li> <code>java.io.File</code>
302  <li> <code>String[]</code>
303  <li> <code>Object</code>
304</ul>
305Any/all of these can be contained in the specified list at the
306same time. If a <code>String[]</code> is found, the <b>0</b>th element
307(i.e., (String[] foo)[0]) is used for comparison purposes. The
308list is sorted by the default {@link String#compareTo(String)} implementation 
309of <code>String</code>.
310
311@param  c the list to be sorted
312**/
313public static void sortByFileName(List c) {
314  Collections.sort(c, new Comparator() {
315    public int compare(Object o1, Object o2) {
316      return getStr(o1).compareTo(getStr(o2));    
317      }
318    private String getStr(Object o) {
319      String str = null;
320      if ( o instanceof File )
321        str = ((File)o).getName();
322      else if (o instanceof String) 
323        str = (String) o;
324      else 
325        str = (o!=null)? o.toString() : null;
326      return str;
327      }   
328    });
329  }
330
331/**
332This method recursively removes a specified file or recursively 
333removes a specified directory. It is needed (as of JDK 1.4) because
334{@link File#delete} lacks the ability to delete a directory if the 
335directory is not empty. 
336<p>
337Internally, this method delegates to {@link File#delete}, so if
338{@link File#delete} follows sym links, then so will this method.
339Be careful !
340
341@param  f the file or directory to be removed
342@return     <code>true</code> on success, <code>false</code> otherwise. Also returns false if
343        the specified file or directory does not exist.
344**/
345public static boolean deepDelete(File f)  
346  {
347  Argcheck.notnull(f, "File was null");
348  if (dbg) System.out.println("deepDelete(): deleting: " + f);
349  
350  boolean ok = true;
351
352  if (! f.exists()) 
353    return false;
354
355  if (f.isFile()) {
356    ok = f.delete();
357    return ok;
358    }
359
360  //f is a directory
361  File[] files = f.listFiles();  //does not return "." or ".."
362  boolean subok = false;
363
364  //1. delete sub directories
365  for (int n = 0; n < files.length; n++) {
366    subok = deepDelete(files[n]);
367    if (! subok) ok = false;
368    } 
369  
370  //2. delete current directory
371  subok = f.delete();
372  if (! subok) ok = false;
373  
374  return ok;  
375  }
376
377/**
378Gets the total size for a directory and all of it's contents. If
379the specified argument is a regular file, returns the size of that
380file itself.
381
382@param  dir   the target dir
383@return     the directory or file size in bytes
384**/
385public static long dirSize(File dir) 
386  {
387  Argcheck.notnull(dir, "File was null");
388  long size = 0;
389
390  if (! dir.exists()) 
391    return 0;
392
393  if (dir.isFile()) {
394    return dir.length();    
395    }
396
397  File[] files = dir.listFiles();  //does not return "." or ".."
398
399  for (int n = 0; n < files.length; n++) 
400    size += dirSize(files[n]);
401    
402  return size;
403  }
404
405/** 
406Buffers and returns the specified InputStream, if it is not already buffered.
407Does not buffer an already buffered stream but returns it as is.
408
409@param in the input stream to be buffered
410@return the buffered stream
411*/
412public static BufferedInputStream bufferStream(InputStream in)
413  {
414  Argcheck.notnull(in, "InputStream was null");
415  BufferedInputStream bin;
416  if (! (in instanceof BufferedInputStream)) {
417    bin = new BufferedInputStream(in);    
418    }
419  else {
420    bin = (BufferedInputStream) in;
421    }
422  return bin;
423  }
424
425/** 
426Buffers and returns the specified OutputStream, if it is not already buffered.
427Does not buffer an already buffered stream but returns it as is.
428
429@param out  the output stream to be buffered
430@return the buffered stream
431
432**/
433public static BufferedOutputStream bufferStream(OutputStream out)
434  {
435  Argcheck.notnull(out, "OutputStream was null");
436  BufferedOutputStream bout;
437  if (! (out instanceof BufferedOutputStream)) {
438    bout =  new BufferedOutputStream(out);    
439    }
440  else {
441    bout = (BufferedOutputStream) out;
442    }
443  return bout;
444  }
445  
446public static BufferedReader bufferReader(Reader in) 
447  {
448  Argcheck.notnull(in, "Reader was null");
449  BufferedReader bin;
450  if ( ! (in instanceof BufferedReader)) {
451    bin = new BufferedReader(in);
452    }
453  else {
454    bin = (BufferedReader) in;
455    }
456  return bin;
457  }
458    
459public static PrintStream toPrintStream(OutputStream out) 
460  {
461  Argcheck.notnull(out, "OutputStream was null");
462  if ( ! (out instanceof PrintStream)) {
463    out = new PrintStream(out);
464    }
465  return (PrintStream) out;
466  }
467  
468public static PrintWriter toPrintWriter(Writer out) 
469  {
470  Argcheck.notnull(out, "Writer was null");
471  if ( ! (out instanceof PrintWriter)) {
472    out = new PrintWriter(out);
473    }
474  return (PrintWriter) out;
475  }
476
477public static BufferedWriter bufferWriter(Writer out) 
478  {
479  Argcheck.notnull(out, "Writer was null");
480  BufferedWriter bout;
481  if ( ! (out instanceof BufferedWriter)) {
482    bout = new BufferedWriter(out);
483    }
484  else {
485    bout = (BufferedWriter) out;
486    }
487  return bout;
488  }
489
490/**
491Convenience method to print the contents of a java.util.Property object
492to a String (using the default platform encoding).
493
494**/
495public static String propertiesToString(Properties props)
496  {
497  String temp = null;
498  final ByteArrayOutputStream bout = new ByteArrayOutputStream();
499  final PrintStream pout = new PrintStream(bout);
500  props.list(pout);
501  pout.flush();
502  temp = bout.toString();
503  pout.close();
504  return temp;
505  }
506
507private static String defaultEncoding = null;
508
509/**
510Returns the default encoding used by the current platform. Returns
511<code>null</code> is the default encoding cannot be determined.
512**/
513public static String getDefaultEncoding() 
514  {
515  if (defaultEncoding != null) 
516    return defaultEncoding;
517  String de = null;
518  try {
519    final InputStream in = ClassLoader.getSystemResourceAsStream("fc/io/IOUtil.class");
520    final InputStreamReader defaultReader = new InputStreamReader(in);
521    de = defaultReader.getEncoding();
522    defaultEncoding = de;
523    if (dbg) System.out.println("IOUtil.getDefaultEncoding() = " + de);
524    }
525  catch (Exception e) {
526    e.printStackTrace();  
527    }
528  return de;
529  }
530
531
532/**
533Returns the contents of an entire file as a List of individual lines. If the specified
534file does not exist or have no content return an empty List. Uses UTF-8 encoding to read the file.
535
536@param  instream    the stream to be read 
537@param  trim      if <code>true</code>, any leading or trailing blank lines are trimmed are ignored.
538@param  comment_chars Regex of comment chars. Any lines that start with this (or have leading spaces 
539            and then start with this) are ignored. (example: <code>#</code> or <code>#|//</code>)
540*/
541public static List fileToLines(InputStream instream, boolean trim, String comment_chars) throws IOException
542  {
543  BufferedReader in = new BufferedReader(new InputStreamReader(instream, "UTF-8"));
544  
545  List list = new ArrayList();
546  String line = null;
547  Pattern pat = null; 
548  Matcher m = null;
549  if (comment_chars != null) {
550    pat = Pattern.compile("^[ \\t]*(" + comment_chars + ")+");
551    }
552    
553  while ( (line = in.readLine()) != null)
554    {
555    if (trim) {
556      line = line.trim();  //this gets rid of spaces, empty newlines, etc
557      if (line.length() == 0) {
558        continue;
559        }
560      }
561      
562    if (pat != null)
563      {
564      m = pat.matcher(line);
565      if (m.find()) {
566        continue;
567        }
568      }
569    list.add(line);
570    }
571  return list;
572  }
573
574/**
575Returns the contents of an entire file as a List of individual lines. If the specified
576file does not exist or have no content return an empty List.
577
578@param  file      the file to be read ("UTF-8" encoding is used)
579@param  trim      if <code>true</code>, any leading or trailing blank lines are trimmed are ignored.
580@param  comment_chars Regex of comment chars. Any lines that start with this (or have leading spaces 
581            and then start with this) are ignored. (example: <code>#</code> or <code>#|//</code>)
582*/
583public static List fileToLines(File file, boolean trim, String comment_chars) throws IOException
584  {
585  return fileToLines(new FileInputStream(file), trim, comment_chars);
586  }
587  
588/**
589Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
590and lines beginning with the following characters are ignored: <code>#</code> and <code>//</code>
591
592@param  file    the file to be read ("UTF-8" encoding is used)
593*/
594public static List fileToLines(File file) throws IOException
595  {
596  return fileToLines(file, true, "#|//");
597  }
598
599/**
600Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
601and lines beginning with the following characters are ignored: <code>#</code> and <code>//</code>
602
603@param  filename  the file to be read ("UTF-8" encoding is used)
604*/
605public static List fileToLines(String filename) throws IOException
606  {
607  return fileToLines(new File(filename));
608  }
609  
610/**
611Returns the contents of an entire file as a List of individual lines. Empty lines are trimmed
612and lines beginning with the following characters are ignored: <code>#</code> and <code>//</code>
613
614@param  in  the input stream to be read
615*/
616public static List fileToLines(InputStream in) throws IOException
617  {
618  return fileToLines(in, true, "#|//");
619  } 
620  
621/** 
622Returns the contents of an entire file as a String. If the specified
623file does not exist returns <code>null</code>. Files that exist but have no
624content return an empty String.
625<p>
626<b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
627less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
628an <code>IOException</code> will be thrown.
629<br>
630<b>Note 2:</b> The files is converted into a String using an encoding
631that is determined programmatically (from the filesystem). This may
632not be totally reliable but there is no way around this because JDK 1.4
633provides <b>no</b> way to easily get the default platform encoding.
634Uses the <code>ISO_8859_1</code> encoding as a fallback measure, if the
635default encoding cannot be determined.
636
637@param  file  the file to be read
638@param  trim    if <code>true</code>, any trailing whitespace is trimmed from the file's end.
639**/
640public static String fileToString(File file, boolean trim) throws IOException
641    {
642  byte[] buf = fileToByteArray(file);
643  if (buf == null)
644    return null;
645  
646  //there is no way to convert a byte buffer to a String
647
648  //because we cannot get a Charset that uses the default platform
649  //encoding in JDK 1.4.0. So we are going to IOUtil.getDefaultEncoding 
650  //method (which is a workaround) to get the default platform encoding.
651  
652  //update: instead of default encoding, arrayToCharBuffer will UTF-8 if 
653  //encoding is not specified.
654
655  // resultstr will be "" if buf contains 0 chars (it can't be null 
656  // if we have reached this point)
657
658  String resultstr = arrayToCharBuffer(buf).toString(); 
659  if (trim) {
660    //might trigger g.c if string size is large
661    resultstr = resultstr.trim();
662    }
663  return resultstr;
664    }
665
666
667/** 
668Returns the contents of an entire file as a String. If the specified
669file does not exist returns <code>null</code>. Files that exist but have no
670content return an empty String.
671<p>
672<b>Note 1:</b> Due to jdk1.4 brain damage, this method is limited to files 
673less than 2^32 bytes. If the specified file is greater than 2^32 bytes,
674an <code>IOException</code> will be thrown.
675<br>
676<b>Note 2:</b> The files is converted into a String using an encoding
677that is determined programmatically (from the filesystem). This may
678not be totally reliable but there is no way around this because JDK 1.4
679provides <b>no</b> way to easily get the default platform encoding.
680Uses the <code>ISO_8859_1</code> encoding as a fallback measure, if the
681default encoding cannot be determined.
682
683@param  filename  the absolute path to the file name to be read
684@param  trim  if <code>true</code>, any trailing whitespace is trimmed from the file's end.
685**/
686public static String fileToString(String filename, boolean trim) throws IOException
687  {
688  return fileToString(new File(filename), trim);
689  }
690
691/** 
692Calls {@link #fileToString(String, boolean)} with trim being
693<code>false</code> (that is, files are not trimmed at their trailing end).
694
695@param  filename  the absolute path to the file name to be read
696**/
697public static String fileToString(String filename) throws IOException
698    {
699    return fileToString(filename, false);
700  }
701
702/** 
703Calls {@link #fileToString(String, boolean)} with trim being
704<code>false</code> (that is, files are not trimmed at their trailing end).
705
706@param  file  the file to be read
707**/
708public static String fileToString(File file) throws IOException
709    {
710    return fileToString(file, false);
711  }
712
713
714/**  
715Returns the contents of an entire file as a <code>byte[]</code>. If the specified
716file does not exist returns <code>null</code>. 
717<p>
718<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
719this method is limited to files less than or equal to 2^32 bytes. If 
720the specified file is greater than 2^32 bytes, an <code>IOException</code> 
721will be thrown.
722
723@param    filename  the absolute path to the file name to be read
724@return ByteBuffer  contains the bytes for that file
725**/
726public static byte[] fileToByteArray(String filename) throws IOException
727  {
728  return fileToByteArray(new File(filename));
729  }
730
731/**  
732Returns the contents of an entire file as a <code>byte[]</code>. If the specified
733file does not exist returns <code>null</code>. 
734<p>
735<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
736this method is limited to files less than or equal to 2^32 bytes. If 
737the specified file is greater than 2^32 bytes, an <code>IOException</code> 
738will be thrown.
739
740@param    file    the file to be read
741@return   byte[]    contains the bytes for that file
742**/
743public static byte[] fileToByteArray(File file) throws IOException
744  {
745  if (dbg) System.out.println("ENTER fileToByteBuffer(" + file + ")");
746
747  Argcheck.notnull(file);
748  
749  if ( ! file.exists() )
750    return null;
751
752  if (dbg) System.out.println("file '" + file + "' exists");
753  
754  long longfsize = file.length();
755  if (dbg) System.out.println("'" + file + "' size = " + longfsize);
756  
757  if ( longfsize > FOUR_GB )
758    throw new IOException("File size of " + longfsize + " too large for this method");  
759
760  FileInputStream fin = new FileInputStream(file);
761  int fsize = (int) longfsize;
762  byte[]  buf = new byte[fsize];
763
764  try {
765    int read, pos = 0;
766    while (pos < fsize) 
767      {
768      /* Usually, this will read everything the first time */
769      read = fin.read(buf, pos, fsize - pos);
770      pos += read;
771      if (read < 0)
772        break;
773      }
774
775    if (dbg) System.out.println("Read file byte[] = " + buf.length + " bytes");
776    
777    if (pos != fsize)
778          throw new IOException( "Can't read entire file, filesize = " + fsize + ", read = " + pos);
779
780    if (dbg) System.out.println("EXIT fileToByteBuffer(" + file + ")");
781    }
782  finally {
783    fin.close(); 
784    }
785
786  return buf;
787  }
788  
789/**  
790Returns the contents of an entire file as a ByteBuffer backed by mapping the
791file to memory. If the specified file does not exist returns <code>null</code>.
792Mapped files do <b>not</b> have have a accesible backing array and the
793<code>ByteBuffer.hasArray()</code> will be <code>false</code>. See the {@link
794java.nio.MappedByteBuffer} documentation about concurrent modification or
795deletion of files that are mapped into memory.
796<p>
797The ByteBuffer returned by this method will have <code>{@link
798ByteBuffer#rewind()} </code> called on it before it is returned.
799<p>
800<b>Note 1:</b> This method is limited to files less than 2^32 bytes, since
801ByteBuffers cannot be greater than this size. If the specified file is greater 
802than 2^32 bytes, an <code>IOException</code> will be thrown.
803
804@param    file    the file to be read
805@return ByteBuffer  contains the bytes for that file
806**/
807public static ByteBuffer fileToByteBuffer(File file) throws IOException
808  {
809  if (dbg) System.out.println("ENTER fileAsByteBuffer(" + file + ")");
810  Argcheck.notnull(file);
811  long fsize = 0;
812
813  if ( ! file.exists() )
814    return null;
815
816  if (dbg) System.out.println("file '" + file + "' exists");
817  
818  fsize = file.length();
819
820  if (dbg) System.out.println("'" + file + "' size = " + fsize);
821  
822  if ( fsize > FOUR_GB)
823    throw new IOException("File size of " + file.length() + " too large for this method"); 
824
825  FileChannel fcin = new FileInputStream(file).getChannel();
826  
827  BufferedReader reader = null;
828  final ByteBuffer bufin = fcin.map(FileChannel.MapMode.READ_ONLY, 0, fsize);
829  
830  if (dbg) System.out.println("File ByteBuffer = " + bufin);
831
832  //This is very important and easy to forget -- rewind the buffer !!!
833  
834  bufin.rewind();
835
836  if (dbg) System.out.println("EXIT fileAsByteBuffer(" + file + ")");
837  return bufin;
838  }
839
840/**  
841Returns the contents of an entire file as a <code>char[]</code>. If the specified
842file does not exist returns <code>null</code>. 
843<p>
844<b>Note 1:</b> Since java arrays cannot be greater than 2^32 elements,
845this method is limited to files less than or equal to 2^32 bytes. If 
846the specified file is greater than 2^32 bytes, an <code>IOException</code> 
847will be thrown.
848
849@param    file    the file to be read
850@param    encoding  the name of the character encoding to use. 
851            Specify <code>null</code> to use UTF-8 encoding.
852@return char[]      contains the chars for that file
853**/
854public static char[] fileToCharArray(File file, String encoding) 
855throws IOException
856  {
857  Argcheck.notnull(file);
858  
859  if ( ! file.exists() )
860    return null;
861
862  if (dbg) System.out.println("file '" + file + "' exists");
863  
864  long longfsize = file.length();
865  if (dbg) System.out.println("'" + file + "' size = " + longfsize);
866  
867  if ( longfsize > FOUR_GB )
868    throw new IOException("File size of " + longfsize + " too large for this method");  
869
870  FileInputStream fin = new FileInputStream(file);
871  
872  if (encoding == null) {
873    encoding = "UTF-8";
874    }
875    
876  final Reader reader = new InputStreamReader(fin, encoding); 
877
878  int fsize = (int) longfsize;
879  char[] buf =  readerToCharArray(reader, (int)fsize);  //buf_fize (fsize) can be greater than chars read
880  
881  /*
882  Can't do it this way - we create a buf of file size bytes but the number of characters
883  read can be less than file size (because of various encodings). This way returns a buf
884  with NULL characters at the end, so either have to trim the buf or just use a char array
885  writer
886  */
887  /*
888  buf = new char[fsize];
889  try {
890    int read, pos = 0;
891    while (pos < fsize) 
892      {
893      //Usually, this will read everything the first time 
894      read = reader.read(buf, pos, fsize - pos);
895      pos += read;
896      if (read < 0) { //EOF
897        break;
898        }
899      }
900  
901    if (dbg && pos != fsize) {
902      System.out.println("File: " +  file.getAbsolutePath() 
903        + " has bytes [" + fsize + "], read [" + pos + "] chars\n"
904        + "This is expected since byte->char will loose characters for non-ascii files. Currently using [" 
905        + encoding + "] to read this file"
906        );
907      }
908    }
909    
910    //at this point buf will have pos characters, not fsize characters.
911  
912  finally {
913    fin.close(); 
914    }
915  */
916  
917  return buf;
918  }
919
920/*
921Reads the entire Stream and returns all read data as a 
922<tt>char[]</tt>. If no data is available, returns an empty char[].
923*/
924public static char[] readerToCharArray(Reader reader) 
925throws IOException
926  {
927  Argcheck.notnull(reader);
928
929  return readerToCharArray(reader, 1024);   
930  }
931
932
933/*
934Reads the entire Stream and returns all read data as a 
935<tt>char[]</tt>. If no data is available, returns an empty char[].
936*/
937public static char[] readerToCharArray(Reader reader, int buffer_size) 
938throws IOException
939  {
940  Argcheck.notnull(reader);
941
942  char[] buf = new char[buffer_size];
943  if (dbg) System.out.println("readerToCharArray(), block=yes");
944
945  final CharArrayWriter cout = new CharArrayWriter(buffer_size);
946  int read = 0;
947  while (true) {
948    read = reader.read(buf, 0, buffer_size);  
949    if (read == -1)
950      break;  
951    cout.write(buf, 0, read);
952    }
953
954  return cout.toCharArray();    
955  }
956
957
958
959
960/** 
961Converts the specified byte array into a CharBuffer using the specified
962encoding. The returned CharBuffer can be directly used in statements such
963as <code>System.out.println</code> to print it's contents,
964<p>
965This method returns <code>null</code> if the specified array is <code>null</code>
966or if the specified encoding is <code>null</code>.
967
968@param  array   the array to convert
969@param  encoding  the {@link java.nio.charset.Charset charset} encoding to use to     
970          convert bytes into chars
971**/
972public static CharBuffer arrayToCharBuffer(byte[] array, String encoding) 
973  {
974  if ( (array == null) || (encoding == null))
975    return null;  
976    
977  Charset cset = Charset.forName(encoding);
978  CharBuffer cbuf = cset.decode(ByteBuffer.wrap(array));
979  return cbuf;
980  }
981
982/** 
983Convenience method that delegates to {@link #arrayToCharBuffer(byte[],
984String)} using UTF-8 encoding by default.
985**/ 
986public static CharBuffer arrayToCharBuffer(byte[] array) 
987  {
988  //String enc = IOUtil.getDefaultEncoding();
989  //enc = (enc != null) ? enc : "ISO-8859-1";
990  //UTF-8 is safer overall
991  return arrayToCharBuffer(array, "UTF-8");
992  } 
993
994/**
995Reads the entire InputStream and returns all read data as a 
996<code>byte[]</code>. If no data is available, returns <code>null</code>.
997
998@param  in    the InputStream to read
999@param  block   if <code>true</code>, this method will block until all 
1000        available data from the specified input stream
1001        has been read. If <code>false</code>, this method will
1002        read and return as much data as currently is available 
1003        is read without blocking. The available amount is 
1004        that returned by the available() method of the specified 
1005        input stream.
1006
1007@throws NegativeArraySizeException  if the specified input stream returns
1008                  a negative number for available()       
1009**/
1010public static byte[] inputStreamToByteArray(InputStream in, boolean block) 
1011throws IOException
1012  {
1013  Argcheck.notnull(in, "InputStream was null");
1014
1015  final BufferedInputStream bin = bufferStream(in);
1016  
1017  if (! block) {
1018    int buffer_size = bin.available();
1019    if (dbg) System.out.println("inputStreamToByteArray(), block=no, buffersize=" + buffer_size);
1020    byte[] buf = new byte[buffer_size];
1021    int read = 0;
1022    int pos = 0;
1023    while (read < buffer_size) {
1024      read += bin.read(buf, pos, buffer_size - read);
1025      pos = read + 1;
1026      }
1027    if (dbg) System.out.println("inputStreamToByteArray(), returning buf=" + buf.length + " bytes");
1028    return buf;
1029    }
1030  
1031  //block
1032  int buffer_size = 1024;
1033  byte[] buf = new byte[buffer_size];
1034  if (dbg) System.out.println("inputStreamToByteArray(), block=yes");
1035
1036  final ByteArrayOutputStream bout = new ByteArrayOutputStream(buffer_size);
1037  int read = 0;
1038  while (true) {
1039    read = bin.read(buf, 0, buffer_size); 
1040    if (read == -1)
1041      break;  
1042    bout.write(buf, 0, read);
1043    }
1044  //if size() is 0, toByteArray returns an array of size 0. we
1045  //return null instead.
1046  if (bout.size() == 0) {
1047    return null;
1048    }
1049  return bout.toByteArray();    
1050  }
1051
1052/**
1053Calls inputStreamToByteArray(in, <code>true</code>)
1054*/
1055public static byte[] inputStreamToByteArray(InputStream in) throws IOException
1056  {
1057  return inputStreamToByteArray(in, true);  
1058  }
1059
1060
1061/**
1062Reads the entire InputStream and returns all read data as a <code>String</code>, using UTF-8
1063encoding. If no data is available, returns <code>null</code>. The specified input stream is
1064<u>not</u> closed.
1065
1066@param  in    the InputStream to read
1067@param  block   if <code>true</code>, this method will block until all 
1068        available data from the specified input stream
1069        has been read. If <code>false</code>, this method will
1070        read and return as much data as currently is available 
1071        is read without blocking. The available amount is 
1072        that returned by the specified input stream.
1073
1074@throws NegativeArraySizeException  if the specified input stream returns
1075                  a negative number for available()
1076**/
1077public static String inputStreamToString(InputStream in, boolean block) 
1078throws IOException
1079  {
1080  final byte[] buf = inputStreamToByteArray(in, block);
1081  if (buf == null) {  
1082    return null;
1083    }
1084  return new String(buf, "UTF-8");    
1085  }   //~inputStreamToString
1086
1087
1088
1089/**
1090Reads the entire InputStream and returns all read data as a 
1091<code>String</code> (using the specified platform encoding). If
1092no data is available, returns <code>null</code>. The specified input
1093stream is <u>not</u> closed.
1094<p>
1095This method will block until all available data from the specified 
1096input stream has been read.
1097
1098@param  in      the input stream to read
1099@param  encoding  the {@link java.nio.charset.Charset} encoding name to use to
1100          convert bytes into chars
1101*/
1102public static String inputStreamToString(InputStream in, String encoding) 
1103throws IOException
1104  {
1105  final byte[] buf = inputStreamToByteArray(in, true);
1106  if (buf == null) {  
1107    return null;
1108    }
1109  return new String(buf, encoding);
1110  }
1111
1112
1113/**
1114Calls inputStreamToString(in, <code>true</code>)
1115*/
1116public static String inputStreamToString(InputStream in) throws IOException
1117  {
1118  return inputStreamToString(in, true);
1119  }
1120
1121
1122/**
1123Convenience method to print the stack trace of an Exception (or Throwable)
1124to a String (using the default platform encoding). (The <code>getMessage()</code>
1125method of a Throwable does not print the entire stack trace).
1126**/
1127public static String throwableToString(final Throwable e)
1128  {
1129  Argcheck.notnull(e, "The specified exception object was null");
1130  String temp = null;
1131  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1132  final PrintStream pout = new PrintStream(bout);
1133  e.printStackTrace(pout);
1134  pout.flush();
1135  temp = bout.toString();
1136  pout.close();
1137  return temp;
1138  }
1139
1140/**
1141Convenience method that returns the current execution stack trace as a String.
1142(using the default platform encoding).
1143**/
1144public static String stackTrace()
1145  {
1146  String temp = null;
1147  final ByteArrayOutputStream bout = new ByteArrayOutputStream(768);  
1148  final PrintStream pout = new PrintStream(bout);
1149  pout.println("==================== Debug Stack Trace ======================");
1150  new Exception().printStackTrace(pout);
1151  pout.println("=============================================================");
1152  pout.flush();
1153  temp = bout.toString();
1154  pout.close();
1155  return temp;
1156  }
1157
1158/** 
1159Calls {@link #fileSizeToString(long)} and truncates the 
1160size to fit in the specified number of digits. For example,
1161a file size of <code>4.455 KB</code> and a length of 2 will 
1162return <code>4.45 KB</code>
1163
1164@param  filesize  the size of the file in bytes
1165@param  length    the max number of digits <b>after</b>
1166          the decimal point
1167*/
1168public static String fileSizeToString(long filesize, int length)
1169  {
1170  NumberFormat nf = NumberFormat.getInstance(); 
1171  nf.setMaximumFractionDigits(length);
1172  return fileSizeToStringImpl(nf, filesize);
1173  }
1174
1175
1176/** 
1177Converts the specified file size into a human readable description. 
1178Similar to the <code>"--human-readable"</code> flag found in various 
1179GNU programs.
1180
1181@param  filesize  the size of the file in bytes
1182*/
1183public static String fileSizeToString(long filesize)
1184  {
1185  NumberFormat nf = NumberFormat.getInstance(); 
1186  return fileSizeToStringImpl(nf, filesize);    
1187  }
1188  
1189private static final String fileSizeToStringImpl(
1190          NumberFormat nf, long filesize)
1191  {
1192  StringBuffer buf = new StringBuffer(32);
1193  if (filesize > ONE_GB) 
1194        buf.append(nf.format(filesize / ONE_GB)).append(" GB");
1195  else if (filesize > ONE_MB) 
1196    buf.append(nf.format( filesize / ONE_MB)).append(" MB");
1197  else if (filesize > ONE_KB)
1198    buf.append(nf.format(filesize / ONE_KB)).append(" KB");
1199  else    
1200    buf.append(nf.format(filesize)).append(" bytes");
1201  
1202  return buf.toString();
1203  }
1204
1205/**
1206Converts a string file size into a number denoting the equivalent bytes. 
1207For example:
1208<pre>
120934K, 34KB --> 34 bytes
121034M, 34megabytes --> 34 * 1024 bytes
1211</pre>
1212Allows numbers to end with <code>k..., m..., g...., b....</code> or no suffix 
1213at all. Suffixes are case insensitive. 
1214*/
1215public static long stringToFileSize(String str)
1216  {
1217  Argcheck.notnull(str, "the specified string was null");
1218  
1219  str = str.replace(" ",""); //remove all leading, trailing, embedded spaces
1220  
1221  int pos = 0;  //this is slighty easier than str.indexOf
1222  for (int n = 0; n < str.length(); n++) 
1223    {
1224    char c = str.charAt(n);
1225    switch (c) {
1226      case 'g': case 'G':
1227        return Long.parseLong(str.substring(0,n)) * ONE_GB; 
1228      case 'm': case 'M':
1229        return Long.parseLong(str.substring(0,n)) * ONE_MB; 
1230      case 'k': case 'K':
1231        return Long.parseLong(str.substring(0,n)) * ONE_KB; 
1232      case 'b': case 'B':
1233        return Long.parseLong(str.substring(0,n)); 
1234      default: 
1235        //nothing to do
1236      }
1237    }
1238  return Long.parseLong(str);
1239  }
1240
1241/**
1242Returns the SHA-1 hash of the specified byte buffer.
1243<p>
1244This method is <b>not</b> thread safe (as far as I can tell, since it uses
1245<code>MessageDigest.getInstance</code>, but <i>if</i> that is thread safe, then
1246this method is thread safe). The invoker should invoke it in a thread safe way.
1247*/
1248public static final String sha1hash(byte[] buf) throws NoSuchAlgorithmException
1249  {
1250  MessageDigest md = MessageDigest.getInstance("SHA-1");
1251  byte[] digest = md.digest(buf);
1252  StringBuilder sb = new StringBuilder();
1253  for (byte b: digest)
1254    {
1255    String hex = Integer.toHexString((int)0xff & b);
1256    if (hex.length()==1) sb.append("0");
1257    sb.append(hex);
1258    }
1259
1260  return sb.toString();
1261  }
1262
1263
1264/** 
1265 Get the InputStream for the specified resource (in the same directory as from where the specified class was loaded). 
1266 Returns null if not found. 
1267 */
1268public static InputStream getClassResource(Class clazz, String resource_name) throws IOException
1269  {
1270  if (clazz == null) {
1271    return null;
1272    }
1273  
1274  if (resource_name.startsWith("/")) {
1275    resource_name = resource_name.substring(1,resource_name.length());
1276    }
1277  InputStream in = clazz.getResourceAsStream(resource_name);
1278  return in;
1279    }
1280  
1281/**
1282Usage:
1283java IOUtil args where args are:
1284  -file full-path-to-file 
1285      [for fileToString and other tests]
1286  
1287**/
1288public static void main(String[] args) throws Exception
1289  {
1290  Args myargs = new Args(args);
1291  String fts = myargs.getRequired("file");
1292  new Test(fts);
1293  }
1294
1295
1296/**
1297Unit Test History   
1298<pre>
1299Class Version Tester  Status      Notes
13001.1       hj    still-testing limited testing only, more needs to be done
1301</pre>
1302*/
1303static private class Test {
1304Test(String fileToString) throws Exception
1305  {
1306  java.io.File sourcefile = new java.io.File("testsourcefile");
1307  java.io.File sourcedir = new java.io.File("testsourcedir"); 
1308  java.io.File destfile = new java.io.File("testdestfile"); 
1309  java.io.File destdir = new java.io.File("testdestdir"); 
1310
1311  String dir1 = "foo";
1312  String dir2 = "foo" + FILE_SEP;     
1313  String f = "f1";
1314  //String r1 = IOUtil.makeFilePath(dir1,f);
1315  //String r2 = IOUtil.makeFilePath(dir2,f);
1316  for(long n = 2; n < 4000000001L; n *= 1000) {
1317    System.out.println("file size " + n + " = " 
1318              + IOUtil.fileSizeToString(n));
1319    }
1320  
1321  System.out.println("Platform encoding, via file.encoding: " + System.getProperty("file.encoding"));
1322  System.out.println("Platform encoding: " + IOUtil.getDefaultEncoding());
1323  System.out.println("--- fileToString('"+ fileToString + "') -----");
1324  String filestr = fileToString(fileToString);
1325  System.out.println("file '" + fileToString + "' read into String"); 
1326  System.out.println("String length = " + filestr.length());
1327  System.out.println("String data = ");
1328  System.out.println(filestr);
1329
1330  List linelist = fileToLines(new File(fileToString));
1331  System.out.println("--- fileToList('"+ fileToString + "') -----");
1332  for (int n = 0; n < linelist.size(); n++) {
1333    System.out.println("[line:" + n + "]: " + linelist.get(n));
1334    }
1335
1336  FileInputStream fin = new FileInputStream(fileToString);
1337  System.out.println("--- inputStreamToString('"+ fileToString + "') -----");
1338  String str = inputStreamToString(fin, false);
1339  System.out.println(str);
1340  System.out.println(fileToString + ", size=" + str.length());
1341
1342  Socket sock = new Socket("www.yahoo.com", 80);
1343  System.out.println("--- inputStreamToString('"+ sock + "') -----");
1344  InputStream sin = sock.getInputStream();
1345  OutputStream sout = sock.getOutputStream();
1346  sout.write("GET /index.html\n\n".getBytes()); 
1347  sout.flush();
1348  System.out.println(inputStreamToString(sin, true));
1349  
1350  Object obj = new Object();
1351  System.out.println("--- getClassResource('"+ obj.getClass() + ", 'String.class') -----");
1352  System.out.println(getClassResource(obj.getClass(), "String"));
1353
1354  System.out.println("--- getClassResource('"+ IOUtil.class + ", 'Args.class') -----");
1355  System.out.println(inputStreamToString(getClassResource(IOUtil.class, "compile.sh")));
1356  
1357  copyFileTest();   
1358  } //~constructor
1359  
1360  void copyFileTest() throws IOException {
1361    File source = new File("/tmp/foo");
1362    File dest = new File("/tmp/bar");     
1363    System.out.println("Copy test: " + source + " to " + dest);
1364    copyDirectory(source, dest);
1365    }
1366} //~Test
1367  
1368}           //~ IOUtil  
1369
1370
1371  
1372  
1373