View Javadoc
1   /*
2    * The baseCode project
3    * 
4    * Copyright (c) 2006 University of British Columbia
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   */
19  package ubic.basecode.util;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileFilter;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.FileReader;
28  import java.io.FileWriter;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.io.PrintWriter;
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Date;
38  import java.util.Enumeration;
39  import java.util.HashSet;
40  import java.util.Iterator;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.StringTokenizer;
44  import java.util.zip.GZIPInputStream;
45  import java.util.zip.ZipEntry;
46  import java.util.zip.ZipFile;
47  
48  import org.apache.commons.lang3.StringUtils;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  /**
53   * @author keshav
54   * @author Pavlidis
55   * @author Will Braynen
56   * 
57   */
58  public class FileTools {
59      private final static String PNG_EXTENSION = ".png";
60      private final static String TXT_EXTENSION = ".txt";
61  
62      public final static String DEFAULT_DATA_EXTENSION = TXT_EXTENSION;
63      public final static String DEFAULT_IMAGE_EXTENSION = PNG_EXTENSION;
64  
65      public final static String DEFAULT_XML_EXTENSION = ".xml";
66      protected final static String[] DATA_EXTENSIONS = { TXT_EXTENSION, ".TXT", "txt.gz", "txt.zip", "txt.gzip" };
67      protected final static String GIF_EXTENSION = ".gif";
68      protected final static String[] IMAGE_EXTENSIONS = { PNG_EXTENSION, GIF_EXTENSION, "PNG", "GIF", "JPEG", "JPG" };
69      protected final static String[] XML_EXTENSIONS = { ".XML", ".RDF-XML", ".rdf-xml.gz", ".rdf-xml.zip", ".xml.zip",
70              ".xml.gz" };
71  
72      private static Logger log = LoggerFactory.getLogger( FileTools.class );
73  
74      /**
75       * @param filename
76       * @return the new filename with the added extension, but does not modify the <code>filename</code> parameter.
77       */
78      public static String addDataExtension( String filename ) {
79          return ( filename + ( FileTools.DEFAULT_DATA_EXTENSION.startsWith( "." ) ? "" : "." )
80                  + FileTools.DEFAULT_DATA_EXTENSION );
81      }
82  
83      /**
84       * @param filename
85       * @return the new filename with the added extension, but does not modify the <code>filename</code> parameter.
86       */
87      public static String addImageExtension( String filename ) {
88          return ( filename + ( FileTools.DEFAULT_IMAGE_EXTENSION.startsWith( "." ) ? "" : "." )
89                  + FileTools.DEFAULT_IMAGE_EXTENSION );
90      }
91  
92      /**
93       * @param filename
94       * @param newExtension
95       * @return the new filename with the changed extension, but does not modify the <code>filename</code> parameter.
96       */
97      public static String changeExtension( String filename, String newExtension ) {
98  
99          String filenameWithoutExtension = chompExtension( filename );
100         return ( filenameWithoutExtension + "." + newExtension );
101     } // end getWithChangedExtension
102 
103     /**
104      * @param file
105      * @throws IOException
106      */
107     public static void checkPathIsReadableFile( String file ) throws IOException {
108         File infile = new File( file );
109         if ( !infile.exists() || !infile.canRead() ) {
110             throw new IOException( "Could not find file: " + file );
111         }
112     }
113 
114     /**
115      * @param filename
116      * @return
117      */
118     public static String chompExtension( String filename ) {
119         int j = filename.lastIndexOf( '.' );
120         if ( j > 1 ) {
121             return filename.substring( 0, filename.lastIndexOf( '.' ) );
122         }
123         return filename;
124     }
125 
126     /**
127      * Avoid getting file names with spaces, slashes, quotes, # etc; replace them with "_".
128      * 
129      * @param ee
130      * @return
131      * @throws IllegalArgumentException if the resulting string is empty, or if the input is empty.
132      */
133     public static String cleanForFileName( String name ) {
134         if ( StringUtils.isBlank( name ) ) throw new IllegalArgumentException( "'name' cannot be blank" );
135         String result = name.replaceAll( "[\\s\'\";,\\/#]+", "_" ).replaceAll( "(^_|_$)", "" );
136         if ( StringUtils.isBlank( result ) ) {
137             throw new IllegalArgumentException( "'" + name + "' was stripped down to an empty string" );
138         }
139         return result;
140     }
141 
142     /**
143      * On completion streams are closed.
144      * 
145      * @param input
146      * @param output
147      * @throws IOException
148      */
149     public static void copy( InputStream input, OutputStream output ) throws IOException {
150 
151         if ( input.available() == 0 ) return;
152 
153         byte[] buf = new byte[1024];
154         int len;
155         while ( ( len = input.read( buf ) ) > 0 ) {
156             output.write( buf, 0, len );
157         }
158         input.close();
159         output.close();
160     }
161 
162     /**
163      * @param sourcePath
164      * @param outputFilePath
165      * @return
166      * @throws FileNotFoundException
167      * @throws IOException
168      */
169     @SuppressWarnings("resource")
170     public static File copyPlainOrCompressedFile( final String sourcePath, String outputFilePath )
171             throws FileNotFoundException, IOException {
172         File sourceFile = new File( sourcePath );
173         if ( !sourceFile.exists() ) {
174             throw new IllegalArgumentException( "Source file (" + sourcePath + ") does not exist" );
175         }
176         if ( sourceFile.exists() && sourceFile.isDirectory() ) {
177             throw new UnsupportedOperationException( "Don't know how to copy directories (" + sourceFile + ")" );
178         }
179 
180         File outputFile = new File( outputFilePath );
181         if ( outputFile.exists() && outputFile.isDirectory() ) {
182             throw new UnsupportedOperationException( "Don't know how to copy to directories (" + outputFile + ")" );
183         }
184 
185         OutputStream out = new FileOutputStream( outputFile );
186 
187         InputStream is = FileTools.getInputStreamFromPlainOrCompressedFile( sourcePath );
188 
189         copy( is, out );
190         return outputFile;
191     }
192 
193     /**
194      * Creates the directory if it does not exist.
195      * 
196      * @param directory
197      * @return
198      */
199     public static File createDir( String directory ) {
200         File dirPath = new File( directory );
201         if ( !dirPath.exists() ) {
202             dirPath.mkdirs();
203         }
204         return dirPath;
205     }
206 
207     /**
208      * Deletes the directory and subdirectories if empty.
209      * 
210      * @param directory
211      * @return int The number of directories deleted.
212      * @see java.io.File#delete()
213      */
214     public static int deleteDir( File directory ) {
215         int numDeleted = 0;
216         Collection<File> directories = listSubDirectories( directory );
217 
218         Iterator<File> iter = directories.iterator();
219         while ( iter.hasNext() ) {
220             File dir = iter.next();
221             if ( dir.listFiles().length == 0 ) {
222                 dir.getAbsoluteFile().delete();
223                 numDeleted++;
224             } else {
225                 log.info( "Directory not empty.  Skipping deletion of " + dir.getAbsolutePath() + "." );
226             }
227         }
228 
229         /* The top level directory */
230         if ( directory.listFiles().length == 0 ) {
231             log.warn( "Deleting " + directory.getAbsolutePath() );
232             directory.getAbsoluteFile().delete();
233             numDeleted++;
234         } else {
235             log.info( "Directory " + directory.getAbsolutePath() + " not empty.  Will not delete." );
236         }
237 
238         if ( numDeleted > 1 ) log.info( "Deleted " + numDeleted + " directories." );
239         return numDeleted;
240     }
241 
242     /**
243      * Deletes the specified <link>Collection<link> of files.
244      * 
245      * @param files
246      * @return int The number of files deleted.
247      * @see java.io.File#delete()
248      */
249     public static int deleteFiles( Collection<File> files ) {
250 
251         int numDeleted = 0;
252 
253         Iterator<File> iter = files.iterator();
254         while ( iter.hasNext() ) {
255 
256             File file = iter.next();
257             if ( file.isDirectory() ) {
258                 log.warn( "Cannot delete a directory." );
259                 continue;
260             }
261 
262             if ( log.isDebugEnabled() ) log.debug( "Deleting file " + file.getAbsolutePath() + "." );
263 
264             if ( file.delete() ) {
265                 numDeleted++;
266             } else {
267                 log.warn( "Failed to delete: " + file + " read=" + file.canRead() + " write=" + file.canWrite() );
268             }
269 
270         }
271         if ( numDeleted > 0 ) log.info( "Deleted " + numDeleted + " files." );
272         return numDeleted;
273     }
274 
275     /**
276      * Returns the extension of a file.
277      * 
278      * @param filename
279      * @return
280      * @return
281      */
282     public static String getExtension( String filename ) {
283         String extension = null;
284         int i = filename.lastIndexOf( '.' );
285 
286         if ( i > 0 && i < filename.length() - 1 ) {
287             extension = filename.substring( i + 1 ).toLowerCase();
288         }
289         return extension;
290     } // end getExtension
291 
292     /**
293      * Open a non-compresed, zipped, or gzipped file. Uses the file name pattern to figure this out.
294      * 
295      * @param fileName. If Zipped, only the first file in the archive is used.
296      * @return
297      * @throws IOException
298      * @throws FileNotFoundException
299      */
300     @SuppressWarnings("resource")
301     public static InputStream getInputStreamFromPlainOrCompressedFile( String fileName )
302             throws IOException, FileNotFoundException {
303         if ( !FileTools.testFile( fileName ) ) {
304             throw new IOException( "Could not read from " + fileName );
305         }
306         InputStream i;
307         if ( FileTools.isZipped( fileName ) ) {
308             log.debug( "Reading from zipped file" );
309             ZipFile f = new ZipFile( fileName );
310             ZipEntry entry = f.entries().nextElement();
311 
312             if ( entry == null ) {
313                 f.close();
314                 throw new IOException( "No zip entries" );
315             }
316 
317             if ( f.entries().hasMoreElements() ) {
318                 log.debug( "ZIP archive has more then one file, reading the first one." );
319             }
320 
321             i = f.getInputStream( entry );
322         } else if ( FileTools.isGZipped( fileName ) ) {
323             log.debug( "Reading from gzipped file" );
324             i = new GZIPInputStream( new FileInputStream( fileName ) );
325         } else {
326             log.debug( "Reading from uncompressed file" );
327             i = new FileInputStream( fileName );
328         }
329         return i;
330     }
331 
332     // is this code duplicated? I can't find any if so
333     /**
334      * opens a file and returns its contents as a list of lines.
335      * 
336      * @return - List of strings representing the lines, first line is first in list
337      * @throws IOException
338      */
339     public static List<String> getLines( File file ) throws IOException {
340         List<String> lines = new LinkedList<String>();
341         BufferedReader in = new BufferedReader( new FileReader( file ) );
342         String line;
343         while ( ( line = in.readLine() ) != null ) {
344             lines.add( line );
345         }
346         in.close();
347         return lines;
348     }
349 
350     /**
351      * opens a file and returns its contents as a list of lines.
352      * 
353      * @return - List of strings representing the lines, first line is first in list
354      * @throws IOException
355      */
356     public static List<String> getLines( String filename ) throws IOException {
357         return getLines( new File( filename ) );
358     }
359 
360     /**
361      * Used for reading output generated by Collection.toString(). For example [a,b,c] stored in a file would be
362      * converted to a new List containing "a", "b" and "c".
363      * <p>
364      * Warning this relies on behaviour of other API's toString.
365      * 
366      * @param f - input file, with only one line for the toString output.
367      * @return - list created from the strings in the file
368      * @throws Exception
369      */
370     public static List<String> getStringListFromFile( File f ) throws Exception {
371         List<String> result = new LinkedList<String>();
372         List<String> lines = FileTools.getLines( f );
373         if ( lines.size() != 1 ) {
374             throw new RuntimeException( "Too many lines in file" );
375         }
376         String line = lines.get( 0 );
377         line = line.substring( 1, line.length() - 1 );
378         StringTokenizer toke = new StringTokenizer( line, "," );
379         while ( toke.hasMoreTokens() ) {
380             result.add( toke.nextToken().trim() );
381         }
382         return result;
383     }
384 
385     /**
386      * @param filename
387      * @return
388      */
389     public static boolean hasImageExtension( String filename ) {
390         for ( int i = 0; i < FileTools.IMAGE_EXTENSIONS.length; i++ ) {
391             if ( filename.toUpperCase().endsWith( FileTools.IMAGE_EXTENSIONS[i].toUpperCase() ) ) {
392                 return true;
393             }
394         }
395         return false;
396 
397     } // end hasImageExtension
398 
399     /**
400      * @param filename
401      * @return
402      */
403     public static boolean hasXMLExtension( String filename ) {
404         for ( int i = 0; i < FileTools.XML_EXTENSIONS.length; i++ ) {
405             if ( filename.toUpperCase().endsWith( FileTools.XML_EXTENSIONS[i].toUpperCase() ) ) {
406                 return true;
407             }
408         }
409         return false;
410     }
411 
412     /**
413      * @param fileName
414      * @return
415      */
416     public static boolean isGZipped( String fileName ) {
417         String capfileName = fileName.toUpperCase();
418         if ( capfileName.endsWith( ".GZ" ) || capfileName.endsWith( ".GZIP" ) ) {
419             return true;
420         }
421         return false;
422     }
423 
424     /**
425      * @param filename
426      * @return
427      */
428     public static boolean isZipped( String filename ) {
429         String capfileName = filename.toUpperCase();
430         if ( capfileName.endsWith( ".ZIP" ) ) {
431             return true;
432         }
433         return false;
434     }
435 
436     /**
437      * Given a File object representing a directory, return a collection of File objects representing the files
438      * contained in that directory.
439      * 
440      * @param directory
441      * @return
442      */
443     public static Collection<File> listDirectoryFiles( File directory ) {
444 
445         if ( !directory.isDirectory() ) throw new IllegalArgumentException( "Must be a directory" );
446 
447         File[] files = directory.listFiles();
448 
449         FileFilter fileFilter = new FileFilter() {
450             @Override
451             public boolean accept( File file ) {
452                 return file.isFile();
453             }
454         };
455         files = directory.listFiles( fileFilter );
456         return Arrays.asList( files );
457     }
458 
459     /**
460      * Given a File object representing a directory, return a collection of File objects representing the directories
461      * contained in that directory.
462      * 
463      * @param directory
464      * @return
465      */
466     public static Collection<File> listSubDirectories( File directory ) {
467 
468         if ( !directory.isDirectory() ) throw new IllegalArgumentException( "Must be a directory" );
469 
470         File[] files = directory.listFiles();
471 
472         FileFilter fileFilter = new FileFilter() {
473             @Override
474             public boolean accept( File file ) {
475                 return file.isDirectory();
476             }
477         };
478         files = directory.listFiles( fileFilter );
479         return Arrays.asList( files );
480     }
481 
482     /**
483      * @param resourcePath
484      * @return
485      * @throws URISyntaxException
486      */
487     public static String resourceToPath( String resourcePath ) throws URISyntaxException {
488         if ( StringUtils.isBlank( resourcePath ) ) throw new IllegalArgumentException();
489         URL resource = FileTools.class.getResource( resourcePath );
490         if ( resource == null ) throw new IllegalArgumentException( "Could not get URL for resource=" + resourcePath );
491         return new File( resource.toURI() ).getAbsolutePath();
492     }
493 
494     /**
495      * Outputs a many strings to a file, one line at a time.
496      */
497     public static void stringsToFile( Collection<String> lines, File f ) throws Exception {
498         stringsToFile( lines, f, false );
499     }
500 
501     /**
502      * Outputs many strings to a file, one line at a time.
503      * 
504      * @param lines - input lines
505      * @param f - file that wrote to
506      * @param append - add to end of file or overwrite
507      * @throws Exception
508      */
509     public static void stringsToFile( Collection<String> lines, File f, boolean append ) throws Exception {
510         PrintWriter fout = new PrintWriter( new FileWriter( f, append ) );
511         for ( String line : lines ) {
512             fout.println( line );
513         }
514         fout.close();
515     }
516 
517     /**
518      * Outputs a many strings to a file, one line at a time.
519      */
520     public static void stringsToFile( Collection<String> lines, String f ) throws Exception {
521         stringsToFile( lines, new File( f ) );
522     }
523 
524     /**
525      * Outputs a string to a file.
526      */
527     public static void stringToFile( String s, File f ) throws Exception {
528         stringToFile( s, f, false );
529     }
530 
531     /**
532      * Outputs a string to a file, one line at a time.
533      * 
534      * @param s - input line/string
535      * @param f - file that wrote to
536      * @param append - add to end of file or overwrite
537      * @throws Exception
538      */
539     public static void stringToFile( String s, File f, boolean append ) throws Exception {
540         FileWriter fout = new FileWriter( f, append );
541         fout.write( s );
542         fout.close();
543     }
544 
545     /**
546      * @param dirname directory name
547      * @return
548      */
549     public static boolean testDir( String dirname ) {
550         if ( dirname != null && dirname.length() > 0 ) {
551             File f = new File( dirname );
552             if ( f.isDirectory() && f.canRead() ) {
553                 return true;
554             }
555         }
556         return false;
557     }
558 
559     /**
560      * Test whether a File is writeable.
561      * 
562      * @param file
563      * @return
564      */
565     public static boolean testFile( File file ) {
566         if ( file != null ) {
567             if ( file.isFile() && file.canRead() ) {
568                 return true;
569             }
570         }
571         return false;
572     }
573 
574     /**
575      * @param filename
576      * @return
577      */
578     public static boolean testFile( String filename ) {
579         if ( filename != null && filename.length() > 0 ) {
580             File f = new File( filename );
581             if ( f.isFile() && f.canRead() ) {
582                 return true;
583             }
584         }
585         return false;
586 
587     }
588 
589     /**
590      * Create or update the modification date of the given file. If the file does not exist, create it.
591      * 
592      * @param f
593      * @throws IOException
594      */
595     public static void touch( File f ) throws IOException {
596         if ( !f.exists() ) {
597             FileWriter w = new FileWriter( f );
598             w.append( "" );
599             w.close();
600         }
601         f.setLastModified( new Date().getTime() );
602     }
603 
604     /**
605      * Given the path to a gzipped-file, unzips it into the same directory. If the file already exists it will be
606      * overwritten.
607      * 
608      * @param seekFile
609      * @throws IOException
610      * @return path to the unzipped file.
611      */
612     public static String unGzipFile( final String seekFile ) throws IOException {
613 
614         if ( !isGZipped( seekFile ) ) {
615             throw new IllegalArgumentException();
616         }
617 
618         checkPathIsReadableFile( seekFile );
619 
620         String outputFilePath = chompExtension( seekFile );
621         File outputFile = copyPlainOrCompressedFile( seekFile, outputFilePath );
622 
623         return outputFile.getAbsolutePath();
624     }
625 
626     /**
627      * @param seekFile
628      * @return Collection of File objects
629      * @throws IOException
630      */
631     @SuppressWarnings("resource")
632     public static Collection<File> unZipFiles( final String seekFile ) throws IOException {
633 
634         if ( !isZipped( seekFile ) ) {
635             throw new IllegalArgumentException();
636         }
637 
638         checkPathIsReadableFile( seekFile );
639 
640         String outputFilePath = chompExtension( seekFile );
641 
642         Collection<File> result = new HashSet<File>();
643         try {
644             ZipFile f = new ZipFile( seekFile );
645             for ( Enumeration<? extends ZipEntry> entries = f.entries(); entries.hasMoreElements(); ) {
646                 ZipEntry entry = entries.nextElement();
647                 String outputFileTitle = entry.getName();
648                 InputStream is = f.getInputStream( entry );
649 
650                 File out = new File( outputFilePath + outputFileTitle );
651                 OutputStream os = new FileOutputStream( out );
652                 copy( is, os );
653 
654                 result.add( out );
655                 log.debug( outputFileTitle );
656             }
657 
658         } catch ( IOException e ) {
659             throw new RuntimeException( e );
660         }
661 
662         return result;
663     }
664 
665 }