Files.java
001 /*
002  *  Files.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  $Id: Files.java 17600 2014-03-08 18:47:11Z markagreenwood $
013  */
014 
015 package gate.util;
016 
017 import gate.Gate;
018 import gate.corpora.DocumentXmlUtils;
019 
020 import java.io.BufferedInputStream;
021 import java.io.BufferedReader;
022 import java.io.File;
023 import java.io.FileFilter;
024 import java.io.FileInputStream;
025 import java.io.FileOutputStream;
026 import java.io.FileReader;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.io.OutputStreamWriter;
030 import java.io.UnsupportedEncodingException;
031 import java.net.URI;
032 import java.net.URISyntaxException;
033 import java.net.URL;
034 import java.nio.charset.CharacterCodingException;
035 import java.nio.charset.Charset;
036 import java.nio.charset.CharsetDecoder;
037 import java.nio.charset.CodingErrorAction;
038 import java.util.ArrayList;
039 import java.util.Arrays;
040 import java.util.HashSet;
041 import java.util.Iterator;
042 import java.util.List;
043 import java.util.Map;
044 import java.util.Set;
045 import java.util.regex.Matcher;
046 import java.util.regex.Pattern;
047 
048 import org.apache.commons.io.IOUtils;
049 
050 
051 /** Some utilities for use with Files and with resources.
052   <P>
053   <B>Note</B> that there is a terminology conflict between the use
054   * of "resources" here and <TT>gate.Resource</TT> and its inheritors.
055   <P>
056   * Java "resources" are files that live on the CLASSPATH or in a Jar
057   * file that are <I>not</I> <TT>.class</TT> files. For example: a
058   <TT>.gif</TT> file that is used by a GUI, or one of the XML files
059   * used for testing GATE's document format facilities. This class
060   * allows you to access these files in various ways (as streams, as
061   * byte arrays, etc.).
062   <P>
063   * GATE resources are components (Java Beans) that provide all of the
064   * natural language processing capabilities of a GATE-based system, and
065   * the language data that such systems analsyse and produce. For
066   * example: parsers, lexicons, generators, corpora.
067   <P>
068   * Where we say "resource" in this class we mean Java resource; elsewhere
069   * in the system we almost always mean GATE resource.
070   */
071 public class Files {
072 
073   /** Debug flag */
074   private static final boolean DEBUG = false;
075 
076   /** Used to generate temporary resources names*/
077   static long resourceIndex = 0;
078 
079   /**Where on the classpath the gate resources are to be found*/
080   protected static final String resourcePath = "/gate/resources";
081 
082   /**Gets the path for the gate resources within the classpath*/
083   public static String getResourcePath(){
084     return resourcePath;
085   }
086 
087   /** It returns the last component in a file path.
088     * It takes E.g: d:/tmp/file.txt and returns file.txt
089     */
090   public static String getLastPathComponent(String path){
091     if(path == null || path.length() == 0return "";
092     //we should look both for "/" and "\" as on windows the file separator is"\"
093     //but a path coming from an URL will be separated by "/"
094     int index = path.lastIndexOf('/');
095     if(index == -1index = path.lastIndexOf('\\');
096     if(index == -1return path;
097     else return path.substring(index + 1);
098   }// getLastPathComponent()
099 
100   /** Get a string representing the contents of a text file. */
101   public static String getString(String fileNamethrows IOException {
102     return getString(new File(fileName));
103   // getString(fileName)
104 
105   /** Get a string representing the contents of a text file. */
106   public static String getString(File textFilethrows IOException {
107     FileInputStream fis = new FileInputStream(textFile);
108     int len = (inttextFile.length();
109     byte[] textBytes = new byte[len];
110     fis.read(textBytes, 0, len);
111     fis.close();
112     return new String(textBytes);
113   // getString(File)
114 
115   /** Get a byte array representing the contents of a binary file. */
116   public static byte[] getByteArray(File binaryFilethrows IOException {
117     FileInputStream fis = new FileInputStream(binaryFile);
118     int len = (intbinaryFile.length();
119     byte[] bytes = new byte[len];
120     fis.read(bytes, 0, len);
121     fis.close();
122     return bytes;
123   // getByteArray(File)
124 
125   /** Get a resource from the GATE ClassLoader as a String.
126     @param resourceName The resource to input.
127     */
128   public static String getResourceAsString(String resourceName)
129   throws IOException {
130     return getResourceAsString(resourceName, null);
131   }
132 
133   /** Get a resource from the GATE ClassLoader as a String.
134     @param encoding The encoding of the reader used to input the file
135     * (may be null in which case the default encoding is used).
136     @param resourceName The resource to input.
137     */
138   public static String
139     getResourceAsString(String resourceName, String encoding)
140     throws IOException
141   {
142     InputStream resourceStream = getResourceAsStream(resourceName);
143     if(resourceStream == nullreturn null;
144     BufferedReader resourceReader;
145     if(encoding == null) {
146       resourceReader = new BomStrippingInputStreamReader(resourceStream);
147     else {
148       resourceReader = new BomStrippingInputStreamReader(resourceStream, encoding);
149     }
150     StringBuffer resourceBuffer = new StringBuffer();
151 
152     int i;
153 
154     int charsRead = 0;
155     final int size = 1024;
156     char[] charArray = new char[size];
157 
158     while( (charsRead = resourceReader.read(charArray,0,size)) != -)
159       resourceBuffer.append (charArray,0,charsRead);
160 
161     while( (i = resourceReader.read()) != -)
162       resourceBuffer.append((chari);
163 
164     resourceReader.close();
165     return resourceBuffer.toString();
166   // getResourceAsString(String)
167 
168   /** Get a resource from the GATE resources directory as a String.
169     * The resource name should be relative to <code>resourcePath</code> which
170     * is equal with <TT>gate/resources</TT>; e.g.
171     * for a resource stored as <TT>gate/resources/jape/Test11.jape</TT>,
172     * this method should be passed the name <TT>jape/Test11.jape</TT>.
173     */
174   public static String getGateResourceAsString(String resourceName)
175     throws IOException {
176     InputStream resourceStream = getGateResourceAsStream(resourceName);
177     if (resourceStream == null)
178       throw new IOException("No such resource on classpath: " + resourceName);
179     try {
180       return IOUtils.toString(resourceStream);
181     }
182     finally {
183       resourceStream.close();
184     }
185   // getGateResourceAsString(String)
186 
187   /**
188     * Writes a temporary file into the default temporary directory,
189     * form an InputStream a unique ID is generated and associated automaticaly
190     * with the file name...
191     */
192   public static File writeTempFile(InputStream contentStream)
193           throws IOException {
194 
195     File resourceFile = null;
196     FileOutputStream resourceFileOutputStream = null;
197 
198     try {
199       // create a temporary file name
200       resourceFile = File.createTempFile("gateResource"".tmp");
201       resourceFileOutputStream = new FileOutputStream(resourceFile);
202       resourceFile.deleteOnExit();
203 
204       if(contentStream == nullreturn resourceFile;
205 
206       int bytesRead = 0;
207       final int readSize = 1024;
208       byte[] bytes = new byte[readSize];
209       while((bytesRead = contentStream.read(bytes, 0, readSize)) != -1)
210         resourceFileOutputStream.write(bytes, 0, bytesRead);
211     finally {
212       IOUtils.closeQuietly(resourceFileOutputStream);
213       IOUtils.closeQuietly(contentStream);
214     }
215 
216     return resourceFile;
217   }// writeTempFile()
218 
219   /**
220     * Writes aString into a temporary file located inside
221     * the default temporary directory defined by JVM, using the specific
222     * anEncoding.
223     * An unique ID is generated and associated automaticaly with the file name.
224     @param aString the String to be written. If is null then the file will be
225     * empty.
226     @param anEncoding the encoding to be used. If is null then the default
227     * encoding will be used.
228     @return the tmp file containing the string.
229     */
230   public static File writeTempFile(String aString, String anEncodingthrows
231       UnsupportedEncodingException, IOException{
232     File resourceFile  = null;
233     OutputStreamWriter writer = null;
234 
235     // Create a temporary file name
236     resourceFile = File.createTempFile ("gateResource"".tmp");
237     resourceFile.deleteOnExit ();
238 
239     if (aString == nullreturn resourceFile;
240     // Prepare the writer
241     if (anEncoding == null){
242       // Use default encoding
243       writer = new OutputStreamWriter(new FileOutputStream(resourceFile));
244 
245     }else {
246       // Use the specified encoding
247       writer = new OutputStreamWriter(
248                       new FileOutputStream(resourceFile),anEncoding);
249     }// End if
250 
251     // This Action is added only when a gate.Document is created.
252     // So, is for sure that the resource is a gate.Document
253     writer.write(aString);
254     writer.flush();
255     writer.close();
256     return resourceFile;
257   }// writeTempFile()
258 
259   /**
260     * Writes aString into a temporary file located inside
261     * the default temporary directory defined by JVM, using the default
262     * encoding.
263     * An unique ID is generated and associated automaticaly with the file name.
264     @param aString the String to be written. If is null then the file will be
265     * empty.
266     @return the tmp file containing the string.
267     */
268   public static File writeTempFile(String aStringthrows IOException{
269     return writeTempFile(aString,null);
270   }// writeTempFile()
271 
272 
273   /** Get a resource from the GATE ClassLoader as a byte array.
274     */
275   public static byte[] getResourceAsByteArray(String resourceName)
276     throws IOException, IndexOutOfBoundsException, ArrayStoreException {
277 
278     InputStream resourceInputStream = getResourceAsStream(resourceName);
279     BufferedInputStream resourceStream =
280       new BufferedInputStream(resourceInputStream);
281     byte b;
282     final int bufSize = 1024;
283     byte[] buf = new byte[bufSize];
284     int i = 0;
285 
286     // get the whole resource into buf (expanding the array as needed)
287     while( (b = (byteresourceStream.read()) != -) {
288       if(i == buf.length) {
289         byte[] newBuf = new byte[buf.length * 2];
290         System.arraycopy (buf,0,newBuf,0,i);
291         buf = newBuf;
292       }
293       buf[i++= b;
294     }
295 
296     // close the resource stream
297     resourceStream.close();
298 
299     // copy the contents of buf to an array of the correct size
300     byte[] bytes = new byte[i];
301     // copy from buf to bytes
302     System.arraycopy (buf,0,bytes,0,i);
303     return bytes;
304   // getResourceAsByteArray(String)
305 
306   /** Get a resource from the GATE resources directory as a byte array.
307     * The resource name should be relative to <code>resourcePath</code> which
308     * is equal with <TT>gate/resources</TT>; e.g.
309     * for a resource stored as <TT>gate/resources/jape/Test11.jape</TT>,
310     * this method should be passed the name <TT>jape/Test11.jape</TT>.
311     */
312   public static byte[] getGateResourceAsByteArray(String resourceName)
313     throws IOException, IndexOutOfBoundsException, ArrayStoreException {
314 
315     InputStream resourceInputStream = getGateResourceAsStream(resourceName);
316     BufferedInputStream resourceStream =
317       new BufferedInputStream(resourceInputStream);
318     byte b;
319     final int bufSize = 1024;
320     byte[] buf = new byte[bufSize];
321     int i = 0;
322 
323     // get the whole resource into buf (expanding the array as needed)
324     while( (b = (byteresourceStream.read()) != -) {
325       if(i == buf.length) {
326         byte[] newBuf = new byte[buf.length * 2];
327         System.arraycopy (buf,0,newBuf,0,i);
328         buf = newBuf;
329       }
330       buf[i++= b;
331     }
332 
333     // close the resource stream
334     resourceStream.close();
335 
336     // copy the contents of buf to an array of the correct size
337     byte[] bytes = new byte[i];
338 
339     // copy from buf to bytes
340     System.arraycopy (buf,0,bytes,0,i);
341     return bytes;
342   // getResourceGateAsByteArray(String)
343 
344 
345   /** Get a resource from the GATE ClassLoader as an InputStream.
346     */
347   public static InputStream getResourceAsStream(String resourceName)
348     throws IOException {
349     // Strip any leading '/'
350     if(resourceName.charAt(0== '/') {
351       resourceName = resourceName.substring(1);
352     }
353 
354     ClassLoader gcl = Gate.getClassLoader();
355     if(gcl == null) {
356       // if the GATE ClassLoader has not been initialised yet (i.e. this
357       // method was called before Gate.init) then fall back to the current
358       // classloader
359       return  Files.class.getClassLoader().getResourceAsStream(resourceName);
360     }
361     else {
362       // if we can, get the resource through the GATE ClassLoader to allow
363       // loading of resources from plugin JARs as well as gate.jar
364       return gcl.getResourceAsStream(resourceName);
365     }
366     //return  ClassLoader.getSystemResourceAsStream(resourceName);
367   // getResourceAsStream(String)
368 
369   /** Get a resource from the GATE resources directory as an InputStream.
370     * The resource name should be relative to <code>resourcePath<code> which
371     * is equal with <TT>gate/resources</TT>; e.g.
372     * for a resource stored as <TT>gate/resources/jape/Test11.jape</TT>,
373     * this method should be passed the name <TT>jape/Test11.jape</TT>.
374     */
375   public static InputStream getGateResourceAsStream(String resourceName)
376     throws IOException {
377 
378     if(resourceName.startsWith("/"|| resourceName.startsWith("\\") )
379       return getResourceAsStream(resourcePath + resourceName);
380     else return getResourceAsStream(resourcePath + "/" + resourceName);
381   // getResourceAsStream(String)
382 
383   /**
384    * Get a resource from the GATE ClassLoader.  The return value is a
385    {@link java.net.URL} that can be used to retrieve the contents of the
386    * resource.
387    */
388   public static URL getResource(String resourceName) {
389     // Strip any leading '/'
390     if(resourceName.charAt(0== '/') {
391       resourceName = resourceName.substring(1);
392     }
393 
394     ClassLoader gcl = Gate.getClassLoader();
395     if(gcl == null) {
396       // if the GATE ClassLoader has not been initialised yet (i.e. this
397       // method was called before Gate.init) then fall back to the current
398       // classloader
399       return Files.class.getClassLoader().getResource(resourceName);
400     }
401     else {
402       // if we can, get the resource through the GATE ClassLoader to allow
403       // loading of resources from plugin JARs as well as gate.jar
404       return gcl.getResource(resourceName);
405     }
406   }
407 
408   /**
409    * Get a resource from the GATE resources directory.  The return value is a
410    {@link java.net.URL} that can be used to retrieve the contents of the
411    * resource.
412    * The resource name should be relative to <code>resourcePath<code> which
413    * is equal with <TT>gate/resources</TT>; e.g.
414    * for a resource stored as <TT>gate/resources/jape/Test11.jape</TT>,
415    * this method should be passed the name <TT>jape/Test11.jape</TT>.
416    */
417   public static URL getGateResource(String resourceName) {
418     if(resourceName.startsWith("/"|| resourceName.startsWith("\\") )
419       return getResource(resourcePath + resourceName);
420     else return getResource(resourcePath + "/" + resourceName);
421   }
422 
423   /**
424    * This method takes a regular expression and a directory name and returns
425    * the set of Files that match the pattern under that directory.
426    *
427    @param regex regular expression path that begins with <code>pathFile</code>
428    @param pathFile directory path where to search for files
429    @return set of file paths under <code>pathFile</code> that matches
430    *  <code>regex</code>
431    */
432   public static Set<String> Find(String regex, String pathFile) {
433     Set<String> regexfinal = new HashSet<String>();
434     String[] tab;
435     File file = null;
436 
437     //open a file
438     try {
439       file = new File(pathFile);
440     catch(NullPointerException npe) {
441       npe.printStackTrace(Err.getPrintWriter());
442     }
443 
444       Pattern pattern = Pattern.compile("^"+regex);
445 
446       if (file.isDirectory()){
447         tab = file.list();
448         for (int i=0;i<=tab.length-1;i++){
449           String finalPath = pathFile+"/"+tab[i];
450           Matcher matcher = pattern.matcher(finalPath);
451           if (matcher.matches()){
452               regexfinal.add(finalPath);
453           }
454         }
455       }
456       else {
457         if (file.isFile()){
458             Matcher matcher = pattern.matcher(pathFile);
459             if (matcher.matches()){
460                 regexfinal.add(pathFile);
461             }
462         }
463       }
464 
465     return regexfinal;
466   //find
467 
468   /** Recursively remove a directory <B>even if it contains other files
469     * or directories</B>. Returns true when the directory and all its
470     * contents are successfully removed, else false.
471     */
472   public static boolean rmdir(File dir) {
473     if(dir == null || ! dir.isDirectory()) // only delete directories
474       return false;
475 
476     // list all the members of the dir
477     String[] members = dir.list();
478 
479     // return value indicating success or failure
480     boolean succeeded = true;
481 
482     // for each member, if is dir then recursively delete; if file then delete
483     for(int i = 0; i<members.length; i++) {
484       File member = new File(dir, members[i]);
485 
486       if(member.isFile()) {
487         if(! member.delete())
488           succeeded = false;
489       else {
490         if(! Files.rmdir(member))
491           succeeded = false;
492       }
493     }
494 
495     // delete the directory itself
496     dir.delete();
497 
498     // return status value
499     return succeeded;
500   // rmdir(File)
501 
502   /**
503    * This method updates an XML element with a new set of attributes.
504    * If the element is not found the XML is unchanged. The attributes
505    * keys and values must all be Strings.
506    *
507    @param xml A stream of the XML data.
508    @param elementName The name of the element to update.
509    @param newAttrs The new attributes to place on the element.
510    @return A string of the whole XML source, with the element updated.
511    */
512   public static String updateXmlElement(
513     BufferedReader xml, String elementName, Map<String,String> newAttrs
514   throws IOException {
515     String line = null;
516     String nl = Strings.getNl();
517     StringBuffer newXml = new StringBuffer();
518 
519     // read the whole source
520     while( ( line = xml.readLine() ) != null ) {
521       newXml.append(line);
522       newXml.append(nl);
523     }
524 
525     // find the location of the element
526     int start = newXml.toString().indexOf("<" + elementName);
527     if(start == -1return newXml.toString();
528     int end =   newXml.toString().indexOf(">", start);
529     if(end == -1)   return newXml.toString();
530 
531     // check if the old element is empty (ends in "/>") or not
532     boolean isEmpty = false;
533     if(newXml.toString().charAt(end - 1== '/'isEmpty = true;
534 
535     // create the new element string with the new attributes
536     StringBuffer newElement = new StringBuffer();
537     newElement.append("<");
538     newElement.append(elementName);
539 
540     // add in the new attributes
541     Iterator<Map.Entry<String,String>> iter = newAttrs.entrySet().iterator();
542     while(iter.hasNext()) {
543       Map.Entry<String,String> entry = iter.next();
544       String key =   entry.getKey();
545       String value = entry.getValue();
546 
547       newElement.append(" ");
548       newElement.append(DocumentXmlUtils.combinedNormalisation(key));
549       newElement.append("=\"");
550       newElement.append(DocumentXmlUtils.combinedNormalisation(value));
551       newElement.append("\"" + nl);
552     }
553 
554     // terminate the element
555     if(isEmptynewElement.append("/");
556     newElement.append(">");
557 
558     // replace the old string
559     newXml.replace(start, end + 1, newElement.toString());
560 
561     return newXml.toString();
562   // updateXmlElement(Reader...)
563 
564   /**
565    * This method updates an XML element in an XML file
566    * with a new set of attributes. If the element is not found the XML
567    * file is unchanged. The attributes keys and values must all be Strings.
568    * We first try to read the file using UTF-8 encoding.  If an error occurs we
569    * fall back to the platform default encoding (for backwards-compatibility
570    * reasons) and try again.  The file is written back in UTF-8, with an
571    * updated encoding declaration.
572    *
573    @param xmlFile An XML file.
574    @param elementName The name of the element to update.
575    @param newAttrs The new attributes to place on the element.
576    @return A string of the whole XML file, with the element updated (the
577    *   file is also overwritten).
578    */
579   public static String updateXmlElement(
580     File xmlFile, String elementName, Map<String,String> newAttrs
581   throws IOException {
582     String newXml = null;
583     BufferedReader utfFileReader = null;
584     BufferedReader platformFileReader = null;
585     Charset utfCharset = Charset.forName("UTF-8");
586     try {
587       FileInputStream fis = new FileInputStream(xmlFile);
588       // try reading with UTF-8, make sure any errors throw an exception
589       CharsetDecoder decoder = utfCharset.newDecoder()
590         .onUnmappableCharacter(CodingErrorAction.REPORT)
591         .onMalformedInput(CodingErrorAction.REPORT);
592       utfFileReader = new BomStrippingInputStreamReader(fis, decoder);
593       newXml = updateXmlElement(utfFileReader, elementName, newAttrs);
594     }
595     catch(CharacterCodingException cce) {
596       // File not readable as UTF-8, so try the platform default encoding
597       if(utfFileReader != null) {
598         utfFileReader.close();
599         utfFileReader = null;
600       }
601       if(DEBUG) {
602         Err.prln("updateXmlElement: could not read " + xmlFile + " as UTF-8, "
603             "trying platform default");
604       }
605       platformFileReader = new BufferedReader(new FileReader(xmlFile));
606       newXml = updateXmlElement(platformFileReader, elementName, newAttrs);
607     }
608     finally {
609       if(utfFileReader != null) {
610         utfFileReader.close();
611       }
612       if(platformFileReader != null) {
613         platformFileReader.close();
614       }
615     }
616 
617     // write the updated file in UTF-8, fixing the encoding declaration
618     newXml = newXml.replaceFirst(
619         "\\A<\\?xml (.*)encoding=(?:\"[^\"]*\"|'[^']*')",
620         "<?xml $1encoding=\"UTF-8\"");
621     FileOutputStream fos = new FileOutputStream(xmlFile);
622     OutputStreamWriter fileWriter = new OutputStreamWriter(fos, utfCharset);
623     fileWriter.write(newXml);
624     fileWriter.close();
625 
626     return newXml;
627   // updateXmlElement(File...)
628 
629 
630   /**
631    * Convert a file: URL to a <code>java.io.File</code>.  First tries to parse
632    * the URL's toExternalForm as a URI and create the File object from that
633    * URI.  If this fails, just uses the path part of the URL.  This handles
634    * URLs that contain spaces or other unusual characters, both as literals and
635    * when encoded as (e.g.) %20.
636    *
637    @exception IllegalArgumentException if the URL is not convertable into a
638    * File.
639    */
640   public static File fileFromURL(URL theURLthrows IllegalArgumentException {
641     try {
642       URI uri = new URI(theURL.toExternalForm());
643       return new File(uri);
644     }
645     catch(URISyntaxException use) {
646       try {
647         URI uri = new URI(theURL.getProtocol(), null, theURL.getPath(), null, null);
648         return new File(uri);
649       }
650       catch(URISyntaxException use2) {
651         throw new IllegalArgumentException("Cannot convert " + theURL + " to a file path");
652       }
653     }
654   }
655 
656   /**
657    * Same as {@link java.io.File#listFiles(java.io.FileFilter)}
658    * but recursive on directories.
659    @param directory file path to start the search, will not be include
660    *   in the results
661    @param filter filter apply to the search
662    @return an array of files (including directories) contained inside
663    *   <code>directory</code>. The array will be empty if the directory is
664    *   empty. Returns null if this abstract pathname does not denote a
665    *   directory, or if an I/O error occurs.
666    */
667   public static File[] listFilesRecursively(File directory, FileFilter filter) {
668     List<File> filesList = new ArrayList<File>();
669 
670     File[] filesRootArray = directory.listFiles(filter);
671     if (filesRootArray == null) { return null}
672 
673     for (File file : filesRootArray) {
674       filesList.add(file);
675       if (file.isDirectory()) {
676         File[] filesDeepArray = listFilesRecursively(file, filter);
677         if (filesDeepArray == null) { return null}
678         filesList.addAll(Arrays.asList(filesDeepArray));
679       }
680     }
681 
682     return filesList.toArray(new File[filesList.size()]);
683   }
684 
685 // class Files