View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  package org.nuiton.util;
24  
25  import org.apache.commons.io.FileUtils;
26  import org.apache.commons.io.IOUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.commons.lang3.SystemUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  import java.io.BufferedInputStream;
33  import java.io.BufferedReader;
34  import java.io.BufferedWriter;
35  import java.io.ByteArrayOutputStream;
36  import java.io.File;
37  import java.io.FileFilter;
38  import java.io.FileInputStream;
39  import java.io.FileOutputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.InputStreamReader;
43  import java.io.OutputStreamWriter;
44  import java.io.PrintStream;
45  import java.nio.CharBuffer;
46  import java.nio.MappedByteBuffer;
47  import java.nio.channels.FileChannel;
48  import java.nio.charset.Charset;
49  import java.nio.charset.CharsetDecoder;
50  import java.nio.charset.StandardCharsets;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.HashMap;
54  import java.util.LinkedList;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.regex.Matcher;
58  import java.util.regex.Pattern;
59  
60  /**
61   * Opérations sur des fichiers. Copie, suppression, renommage,
62   * recherche, fichiers d'un répertoire, sous-répertoires d'un répertoire,
63   * récupération du basename ou de l'extension, création d'un fichier
64   * temporaire, comparaison de dates de création, récupération d'une chaîne,
65   * d'un Reader ou d'un Writer à partir d'un fichier, récupération du fichier
66   * saisi dans une boîte de dialogue, conversions en byte[], en Stream...
67   * <p>
68   * Created: 22 novembre 2004
69   *
70   * @author Benjamin Poussin - poussin@codelutin.com
71   * @author Tony Chemit - chemit@codelutin.com
72   */
73  public class FileUtil { // FileUtil
74  
75      private static final Log log = LogFactory.getLog(FileUtil.class);
76  
77      /**
78       * Encoding utilisé (peut être redéfini)
79       */
80      // TODO fdesbois 2011-04-16 : Perhaps change ISO encoding by UTF-8
81      public static final String ENCODING = StandardCharsets.ISO_8859_1.name();
82  
83      /**
84       * Permet de convertir des bytes en fichier, le fichier sera automatiquement
85       * supprimé a la fin de la JVM.
86       *
87       * @param bytes the array of bytes to copy in dstination file
88       * @return le fichier temporaire contenant les bytes
89       * @throws IOException if any io pb
90       */
91      public static File byteToFile(byte[] bytes) throws IOException {
92          File file = File.createTempFile("FileUtil-byteToFile", ".tmp");
93          FileUtils.writeByteArrayToFile(file, bytes);
94          return file;
95      }
96  
97      /**
98       * Retourne un Reader utilisant l'encoding par defaut {@link #ENCODING}.
99       *
100      * @param file the given reader
101      * @return the reader on the given file
102      * @throws IOException if any io pb
103      */
104     public static BufferedReader getReader(File file) throws IOException {
105         return getReader(file, ENCODING);
106     }
107 
108     /**
109      * Retourne un reader utilisant l'encoding choisi et placé dans un
110      * BufferedReader
111      *
112      * @param file     the given file
113      * @param encoding (ISO-8859-1, UTF-8, ...)
114      * @return the buffered reader in the given encoding
115      * @throws IOException if any io pb
116      */
117     public static BufferedReader getReader(File file,
118                                            String encoding) throws IOException {
119         FileInputStream inf = new FileInputStream(file);
120         InputStreamReader in = new InputStreamReader(inf, encoding);
121         BufferedReader result = new BufferedReader(in);
122         return result;
123     }
124 
125     /**
126      * Retourne un Writer utilisant l'encoding par defaut {@link #ENCODING}.
127      *
128      * @param file the given file
129      * @return the writer on the given file
130      * @throws IOException if any io pb
131      */
132     public static BufferedWriter getWriter(File file) throws IOException {
133         return getWriter(file, ENCODING);
134     }
135 
136     /**
137      * Retourne un writer utilisant l'encoding choisi et placé dans un
138      * BufferedWriter
139      *
140      * @param file     the given file
141      * @param encoding (ISO-8859-1, UTF-8, ...)
142      * @return the buffered writer on the given file with given encoding
143      * @throws IOException if any io pb
144      */
145     public static BufferedWriter getWriter(File file,
146                                            String encoding) throws IOException {
147         FileOutputStream outf = new FileOutputStream(file);
148         OutputStreamWriter out = new OutputStreamWriter(outf, encoding);
149         BufferedWriter result = new BufferedWriter(out);
150         return result;
151     }
152 
153 
154     /**
155      * Permet de creer un nouveu repertoire temporaire, l'effacement du
156      * répertoire est a la charge de l'appelant
157      *
158      * @param prefix le prefix du fichier
159      * @param suffix le suffix du fichier
160      * @param tmpdir le répertoire temporaire ou il faut creer le repertoire
161      *               si null on utilise java.io.tmpdir
162      * @return le fichier pointant sur le nouveau repertoire
163      * @throws IOException if any io pb
164      */
165     public static File createTempDirectory(String prefix,
166                                            String suffix,
167                                            File tmpdir) throws IOException {
168         if (tmpdir == null) {
169             tmpdir = new File(System.getProperty("java.io.tmpdir"));
170         }
171         File result = new File(tmpdir, prefix + System.currentTimeMillis() + suffix);
172         while (result.exists()) {
173             result = new File(tmpdir, prefix + System.currentTimeMillis() + suffix);
174         }
175         if (!result.mkdirs()) {
176             throw new IOException("Can't create temporary directory: " + result);
177         }
178         return result;
179     }
180 
181     /**
182      * Permet de creer un nouveu repertoire temporaire, l'effacement du
183      * répertoire est a la charge de l'appelant
184      *
185      * @param prefix le prefix du repertoire a creer
186      * @param suffix le suffix du repertoire a creer.
187      * @return the temprary created file
188      * @throws IOException if any io pb
189      */
190     public static File createTempDirectory(String prefix,
191                                            String suffix) throws IOException {
192         return createTempDirectory(prefix, suffix, null);
193     }
194 
195     /**
196      * Permet de donner une representation fichier pour une chaine de caractere.
197      * Le fichier sera automatiquement effacé à la fin de la JVM.
198      *
199      * @param content le contenu du fichier temporaire
200      * @return le fichier qui contient content
201      * @throws IOException if any io pb
202      */
203     public static File getTempFile(String content) throws IOException {
204         return getTempFile(content, "");
205     }
206 
207     /**
208      * Permet de donner une representation fichier pour une chaine de caractere.
209      * Le fichier sera automatiquement effacé à la fin de la JVM.
210      *
211      * @param content    le contenu du fichier temporaire
212      * @param fileSuffix l'extension du fichier créé
213      * @return le fichier qui contient content
214      * @throws IOException if any io pb
215      */
216     public static File getTempFile(String content,
217                                    String fileSuffix) throws IOException {
218         File result = File.createTempFile("tmp-" + FileUtil.class.getName(),
219                 fileSuffix);
220         result.deleteOnExit();
221         FileUtils.write(result, content, Charset.defaultCharset());
222         return result;
223     }
224 
225     /**
226      * Equivalent de la methode basename unix.
227      * basename("/tmp/toto.xml", ".xml") → "toto"
228      *
229      * @param file     le fichier dont on souhaite le nom sans le chemin
230      * @param suffixes si present represente le suffixe a eliminer du fichier
231      *                 s'il est trouvé
232      * @return le nom du fichier sans le suffixe si trouvé.
233      */
234     public static String basename(File file, String... suffixes) {
235         String result = basename(file.getName(), suffixes);
236         return result;
237     }
238 
239     /**
240      * Equivalent de la methode basename unix.
241      * basename("/tmp/toto.xml", ".xml") → "toto"
242      *
243      * @param name     le nom du fichier dont on souhaite le nom sans le chemin
244      * @param suffixes si present represente le suffixe a eliminer du fichier
245      *                 s'il est trouvé
246      * @return le nom du fichier sans le suffixe si trouvé.
247      * @since 1.4.2
248      */
249     public static String basename(String name, String... suffixes) {
250         String result = name;
251         for (String suffixe : suffixes) {
252             if (result.endsWith(suffixe)) {
253                 result = result.substring(0,
254                         result.length() - suffixe.length());
255                 break;
256             }
257         }
258         return result;
259     }
260 
261     /**
262      * Permet de récupérer l'extension d'un fichier
263      *
264      * @param file     le fichier dont on souhaite l'extension
265      * @param extchars la liste des caracteres pouvant former l'extension
266      *                 dans l'ordre de preference. Si vide on utilise ".".
267      * @return l'extension ou la chaine vide si le fichier n'a pas d'extension
268      * l'extension ne contient pas le chaine de delimitation
269      */
270     public static String extension(File file, String... extchars) {
271         String name = file.getName();
272         String result = extension(name, extchars);
273         return result;
274     }
275 
276     /**
277      * Permet de récupérer l'extension d'un nom de fichier
278      *
279      * @param name     le nom du fichier dont on souhaite l'extension
280      * @param extchars la liste des caracteres pouvant former l'extension
281      *                 dans l'ordre de preference. Si vide on utilise ".".
282      * @return l'extension ou la chaine vide si le fichier n'a pas d'extension
283      * l'extension ne contient pas le chaine de delimitation
284      * @since 1.4.2
285      */
286     public static String extension(String name, String... extchars) {
287         String result = "";
288 
289         if (extchars.length == 0) {
290             extchars = new String[]{"."};
291         }
292         for (String extchar : extchars) {
293             int pos = name.lastIndexOf(extchar);
294             if (pos != -1) {
295                 result = name.substring(pos + extchar.length());
296                 break;
297             }
298         }
299         return result;
300     }
301 
302     /**
303      * Recupère le fichier dans le même répertoire que le fichier donné et dont
304      * on a changé l'extension.
305      *
306      * @param file         le fichier d'origine
307      * @param newExtension la nouvelle extension à utiliser
308      * @param extchars     la liste des extensions possibles
309      * @return le fichier dont on a changé l'extension
310      * @throws IOException si aucune extension trouvé dans le fichier d'origine
311      * @since 1.4.2
312      */
313     public static File changeExtension(File file,
314                                        String newExtension,
315                                        String... extchars) throws IOException {
316         String name = file.getName();
317         String newName = changeExtension(name, newExtension, extchars);
318         File newFile = new File(file.getParentFile(), newName);
319         return newFile;
320     }
321 
322     /**
323      * Change l'extension du fichier en entrée avec la nouvelle extension
324      *
325      * @param name         le nom de fichier à transformer
326      * @param newExtension la nouvelle extension à utiliser
327      * @param extchars     la liste des extensions possibles
328      * @return le nouveau nom de fichier
329      * @throws IOException si aucune extension trouvé dans le fichier d'origine
330      * @since 1.4.2
331      */
332     public static String changeExtension(String name,
333                                          String newExtension,
334                                          String... extchars) throws IOException {
335         String extension = extension(name, extchars);
336         if (extension == null) {
337             throw new IOException("Could not find extension for name " +
338                     name + " within " + Arrays.toString(extchars));
339         }
340         String nameWithoutExtension = name.substring(
341                 0, name.length() - extension.length());
342         String newName = nameWithoutExtension + newExtension;
343         return newName;
344     }
345 
346     /**
347      * Recupère le fichier mirroir du fichier {@code file} donnée qui est dans
348      * l'arborescence de {@code inputDirectory} dans le répertoire
349      * {@code ouputDirectory}.
350      *
351      * @param inputDirectory  le répertoire de départ
352      * @param outputDirectory le répertoire cible
353      * @param file            le fichier
354      * @return le fichier mirroir dans le répertoire cible
355      * @since 1.4.2
356      */
357     public static File getRelativeFile(File inputDirectory,
358                                        File outputDirectory,
359                                        File file) {
360         String inputPath = inputDirectory.getAbsolutePath();
361         String s = file.getAbsolutePath();
362         int index = s.indexOf(inputPath);
363         if (index == -1) {
364             throw new IllegalArgumentException(
365                     "File " + file + " is not in " + inputDirectory);
366         }
367         String relativePath = s.substring(inputPath.length());
368         File result = new File(outputDirectory, relativePath);
369         return result;
370     }
371 
372     public interface FileAction {
373         boolean doAction(File f);
374     }
375 
376     /**
377      * Retourne tous les sous répertoires du répertoire passé en argument.
378      *
379      * @param directory un répertoire
380      * @return une liste d'objet {@link File} de répertoires et ceci
381      * recursivement à partir de directory, si directory
382      * n'est pas un répertoire la liste est vide.
383      */
384     public static List<File> getSubDirectories(File directory) {
385         class DirectoryFilter implements FileFilter {
386             @Override
387             public boolean accept(File f) {
388                 return f.isDirectory();
389             }
390         }
391         return getFilteredElements(directory, new DirectoryFilter(), true);
392     }
393 
394     /**
395      * Retourne tous les fichiers du répertoire passé en argument.
396      *
397      * @param directory un répertoire
398      * @return une liste d'objet {@link File} des fichiers et ceci
399      * recursivement à partir de directory, si directory n'est pas un
400      * répertoire la liste est vide
401      */
402     public static List<File> getFiles(File directory) {
403         class NormalFileFilter implements FileFilter {
404             @Override
405             public boolean accept(File f) {
406                 return f.isFile();
407             }
408         }
409         return getFilteredElements(directory, new NormalFileFilter(), true);
410     }
411 
412     /**
413      * Retourne les fichiers d'un répertoire qui satisfont un certain pattern.
414      * La recherche est faite récursivement dans les sous répertoires
415      *
416      * @param directory   le répertoire à partir duquel il faut faire la recherche
417      * @param pattern     le pattern que doit respecter le fichier pour être dans la
418      *                    liste résultante
419      * @param recursively flag pour indiquer si on doit descendre dans les sous répertoires
420      * @return une liste d'objet {@link File} qui ont s'attisfait le
421      * pattern.
422      */
423     public static List<File> find(File directory,
424                                   final String pattern,
425                                   boolean recursively) {
426         final String root = directory.getAbsolutePath();
427         final int rootLength = root.length();
428 
429         return getFilteredElements(directory, new FileFilter() {
430             @Override
431             public boolean accept(File f) {
432                 String longFilename = f.getAbsolutePath();
433                 // + 1 to remove the first / or \
434                 String filename = longFilename.substring(rootLength + 1);
435                 return filename.matches(pattern);
436             }
437         }, recursively);
438     }
439 
440     /**
441      * Retourne la liste de toutes les fichiers ou répertoire qui s'attisfont
442      * le filtre
443      *
444      * @param directory   repertoire à partir duquel il faut faire la recherche
445      * @param ff          le filtre à appliquer pour savoir si le fichier parcouru doit
446      *                    être conservé dans les résultats, ou null pour tous les fichiers
447      * @param recursively un flag pour indiquer si on doit descendre dans les répertoires
448      * @return une liste d'objet {@link File}, qui s'attisfont le filtre
449      */
450     public static List<File> getFilteredElements(File directory,
451                                                  FileFilter ff,
452                                                  boolean recursively) {
453         ArrayList<File> result = new ArrayList<File>();
454         LinkedList<File> todo = new LinkedList<File>();
455         if (directory.isDirectory()) {
456             todo.addAll(Arrays.asList(directory.listFiles()));
457         }
458         while (todo.size() > 0) {
459             File file = todo.removeFirst();
460             if (recursively && file.isDirectory()) {
461                 File[] childs = file.listFiles();
462                 if (childs != null) {
463                     // null if we don't have access to directory
464                     todo.addAll(Arrays.asList(childs));
465                 }
466             }
467             if (ff == null || ff.accept(file)) {
468                 result.add(file);
469             }
470         }
471         return result;
472     }
473 
474     /**
475      * Permet de faire une action avant le parcours des fichiers, c-a-d que
476      * l'on fera l'action sur les fichiers contenu dans un répertoire
477      * après l'action sur le répertoire lui même.
478      *
479      * @param f          le fichier ou répertoire à partir duquel il faut commencer
480      * @param fileAction l'action à effectuer sur chaque fichier
481      * @return le résultat des fileAction executé sur les fichiers, chaque
482      * résultat de FileAction sont assemblé par un ET logique pour donner
483      * le résultat final
484      */
485     public static boolean walkAfter(File f, FileAction fileAction) {
486         boolean result = fileAction.doAction(f);
487         if (f.isDirectory()) {
488             File list[] = f.listFiles();
489             for (File aList : list) {
490                 result = result && walkAfter(aList, fileAction);
491             }
492         }
493         return result;
494     }
495 
496     /**
497      * Permet de faire une action apès le parcours des fichiers, c-a-d que
498      * l'on fera l'action sur les fichiers contenu dans un répertoire
499      * avant l'action sur le répertoire lui même.
500      *
501      * @param f          le fichier ou répertoire à partir duquel il faut commencer
502      * @param fileAction l'action à effectuer sur chaque fichier
503      * @return le résultat des fileAction executé sur les fichiers, chaque
504      * résultat de FileAction sont assemblé par un ET logique pour donner
505      * le résultat final
506      */
507     public static boolean walkBefore(File f, FileAction fileAction) {
508         boolean result = true;
509         if (f.isDirectory()) {
510             File list[] = f.listFiles();
511             for (File aList : list) {
512                 result = result && walkBefore(aList, fileAction);
513             }
514         }
515         return result && fileAction.doAction(f);
516     }
517 
518     /**
519      * Copie recursivement le repertoire source dans le repertoire destination
520      * <p>
521      * copyRecursively("/truc/titi", "/var/tmp") donnera le repertoire
522      * "/var/tmp/titi"
523      *
524      * @param srcDir          le répertoire source à copier
525      * @param destDir         le répertoire destination où copier
526      * @param includePatterns les patterns que doivent resperter les
527      *                        fichiers/repertoires pour etre copié. Si vide alors tout est copié
528      * @throws IOException if any io pb
529      */
530     public static void copyRecursively(File srcDir,
531                                        File destDir,
532                                        String... includePatterns) throws IOException {
533         copyAndRenameRecursively(srcDir, destDir, null, null, includePatterns);
534     }
535 
536     /**
537      * Copie recursivement le repertoire source dans le repertoire destination
538      * <p>
539      * copyRecursively("/truc/titi", "/var/tmp", "bidulle") donnera le repertoire
540      * "/var/tmp/bidulle", 'bidulle' remplacant 'titi'
541      *
542      * @param srcDir          le répertoire source à copier
543      * @param destDir         le répertoire destination où copier
544      * @param renameFrom      pattern to permit rename file before uncompress it
545      * @param renameTo        new name for file if renameFrom is applicable to it
546      *                        you can use $1, $2, ... if you have '(' ')' in renameFrom
547      * @param includePatterns les patterns que doivent resperter les
548      *                        fichiers/repertoires pour etre copié. Si vide alors tout est copié
549      * @throws IOException if any io pb
550      */
551     public static void copyAndRenameRecursively(File srcDir, File destDir,
552                                                 String renameFrom,
553                                                 String renameTo,
554                                                 String... includePatterns) throws IOException {
555         copyAndRenameRecursively(srcDir,
556                 destDir,
557                 true,
558                 renameFrom,
559                 renameTo,
560                 false,
561                 includePatterns
562         );
563     }
564 
565     /**
566      * Copie recursivement le repertoire source dans le repertoire destination
567      * <p>
568      * copyRecursively("/truc/titi", "/var/tmp", "bidulle") donnera le repertoire
569      * "/var/tmp/bidulle", 'bidulle' remplacant 'titi'
570      *
571      * @param srcDir          le répertoire source à copier
572      * @param destDir         le répertoire destination où copier
573      * @param includeSrcDir   si vrai alors le repertoire source est copie dans le
574      *                        repertoire destination et non pas seulement les fichiers qu'il contient
575      * @param renameFrom      pattern to permit rename file before uncompress it
576      * @param renameTo        new name for file if renameFrom is applicable to it
577      *                        you can use $1, $2, ... if you have '(' ')' in renameFrom
578      * @param exclude         inverse include pattern interpretation
579      * @param includePatterns les patterns que doivent resperter les
580      *                        fichiers/repertoires pour etre copié. Si vide alors tout est copié
581      * @throws IOException if any io pb
582      */
583     public static void copyAndRenameRecursively(File srcDir,
584                                                 File destDir,
585                                                 boolean includeSrcDir,
586                                                 String renameFrom,
587                                                 String renameTo,
588                                                 boolean exclude,
589                                                 String... includePatterns) throws IOException {
590         String rootSrc;
591         if (includeSrcDir) {
592             rootSrc = srcDir.getParent();
593         } else {
594             rootSrc = srcDir.getPath();
595         }
596         List<File> files = getFilteredElements(srcDir, null, true);
597         log.debug("copyRecursively: " + files);
598         for (File file : files) {
599             boolean doCopy = copyRecursivelyAccept(file, includePatterns);
600             if (exclude ^ doCopy) {
601                 String path = file.getPath().substring(rootSrc.length());
602                 if (renameFrom != null && renameTo != null) {
603                     String tmp = path.replaceAll(renameFrom, renameTo);
604                     if (log.isDebugEnabled()) {
605                         log.debug("rename " + path + " → " + tmp);
606                     }
607                     path = tmp;
608                 }
609 
610                 File destFile = new File(destDir, path);
611                 if (file.isDirectory()) {
612                     log.debug("create directory: " + destFile);
613                     //fixme on doit tester le retour de la méthode, car il se peut que le répertoire
614                     // ne puisse être copié.
615                     createDirectoryIfNecessary(destFile);
616                 } else {
617                     log.debug("copy " + path + " to " + destFile);
618                     FileUtils.copyFile(file, destFile);
619                 }
620             }
621         }
622     }
623 
624     /**
625      * Get a ByteArrayOutputStream containing all data that could be read from the given InputStream
626      *
627      * @param inputStream       the stream to read
628      * @param defaultBufferSize the buffer size
629      * @return the input stream read for input
630      * @throws IOException if any pb while reading or writing
631      */
632     public static ByteArrayOutputStream readBytesFrom(InputStream inputStream,
633                                                       int defaultBufferSize) throws IOException {
634 
635         ByteArrayOutputStream outputStream = new ByteArrayOutputStream(
636                 defaultBufferSize);
637         byte[] buffer = new byte[defaultBufferSize];
638 
639         int readBytes = inputStream.read(buffer);
640         while (readBytes > 0) {
641             outputStream.write(buffer, 0, readBytes);
642             readBytes = inputStream.read(buffer);
643         }
644 
645         return outputStream;
646     }
647 
648     /**
649      * @param file            le fichier à tester.
650      * @param includePatterns les patterns pour accepeter le fichier depuis son nom
651      * @return {@code true} si le fichier est accepté, <code>false> autrement.
652      */
653     private static boolean copyRecursivelyAccept(File file,
654                                                  String[] includePatterns) {
655         boolean result = includePatterns.length == 0;
656         String filename = file.getAbsolutePath();
657         for (String pattern : includePatterns) {
658             result = filename.matches(pattern);
659             if (result) {
660                 break;
661             }
662         }
663         return result;
664     }
665 
666     /**
667      * Use the linePattern to break the given CharBuffer into lines, applying
668      * the input pattern to each line to see if we have a match
669      * <p>
670      * Code taken from :
671      * <p>
672      * http://java.sun.com/javase/6/docs/technotes/guides/io/example/Grep.java
673      *
674      * @param regex regex to search into file
675      * @param cb    nio buffer
676      * @return matching lines (or {code null} if no matching lines)
677      * @since 1.1.2
678      */
679     protected static List<CharSequence> grep(String regex,
680                                              CharBuffer cb) {
681 
682         List<CharSequence> linesList = null;
683 
684         Pattern pattern = Pattern.compile(regex);
685 
686         Pattern linePattern = Pattern.compile(".*\r?\n");
687 
688         Matcher lm = linePattern.matcher(cb);   // Line matcher
689         Matcher pm = null;          // Pattern matcher
690         //int lines = 0;
691         while (lm.find()) {
692             //lines++;
693             CharSequence cs = lm.group();   // The current line
694             if (pm == null) {
695                 pm = pattern.matcher(cs);
696             } else {
697                 pm.reset(cs);
698             }
699             if (pm.find()) {
700                 // init
701                 if (linesList == null) {
702                     linesList = new ArrayList<CharSequence>();
703                 }
704                 linesList.add(cs);
705             }
706             if (lm.end() == cb.limit()) {
707                 break;
708             }
709         }
710 
711         return linesList;
712     }
713 
714     /**
715      * Java implementation for the unix grep command.
716      * <p>
717      * Code taken from :
718      * <p>
719      * http://java.sun.com/javase/6/docs/technotes/guides/io/example/Grep.java
720      * <p>
721      * May fail on windows with error :
722      * The requested operation cannot be performed on a file with a user-mapped section open
723      *
724      * @param searchRegex regex to search into file
725      * @param f           file to search into
726      * @param encoding    encoding to use
727      * @return matching lines (or {code null} if no matching lines)
728      * @throws IOException FIXME
729      * @since 1.1.2
730      */
731     public static List<CharSequence> grep(String searchRegex,
732                                           File f,
733                                           String encoding) throws IOException {
734 
735         List<CharSequence> lines = null;
736 
737         FileInputStream fis = null;
738         FileChannel fc = null;
739 
740         try {
741             // Open the file and then get a channel from the stream
742             fis = new FileInputStream(f);
743             fc = fis.getChannel();
744 
745             // Get the file's size and then map it into memory
746             int sz = (int) fc.size();
747             MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
748 
749             // Decode the file into a char buffer
750             Charset charset = Charset.forName(encoding);
751             CharsetDecoder decoder = charset.newDecoder();
752             CharBuffer cb = decoder.decode(bb);
753 
754             // Perform the search
755             lines = grep(searchRegex, cb);
756         } finally {
757             // Close the channel and the stream
758             if (fc != null) {
759                 fc.close();
760             }
761             if (fis != null) {
762                 fis.close();
763             }
764         }
765 
766         return lines;
767     }
768 
769     /**
770      * Java implementation for the unix grep command.
771      * <p>
772      * May fail on windows with error :
773      * The requested operation cannot be performed on a file with a user-mapped section open
774      *
775      * @param searchRegex   regex to search into file
776      * @param rootDirectory directory to seacrh into
777      * @param fileRegex     regex for file to find in {@code rootDirectory}
778      * @param encoding      encoding to use
779      * @return all matching lines for each files
780      * @throws IOException FIXME
781      * @since 1.1.2
782      */
783     public static Map<File, List<CharSequence>> grep(String searchRegex,
784                                                      File rootDirectory,
785                                                      String fileRegex,
786                                                      String encoding) throws IOException {
787         Map<File, List<CharSequence>> results =
788                 new HashMap<File, List<CharSequence>>();
789         List<File> files = find(rootDirectory, fileRegex, true);
790         for (File file : files) {
791             List<CharSequence> lines = grep(searchRegex, file, encoding);
792             if (lines != null) {
793                 results.put(file, lines);
794             }
795         }
796         return results;
797     }
798 
799     /**
800      * Search for files matching regex in current directory.
801      * <p>
802      * May fail on windows with error :
803      * The requested operation cannot be performed on a file with a user-mapped section open
804      *
805      * @param searchRegex regex to search into file
806      * @param fileRegex   regex for file to find in current dir
807      * @param encoding    encoding to use
808      * @return all matching lines for each files
809      * @throws IOException FIXME
810      * @since 1.1.2
811      */
812     public static Map<File, List<CharSequence>> grep(String searchRegex,
813                                                      String fileRegex,
814                                                      String encoding) throws IOException {
815         Map<File, List<CharSequence>> results = grep(searchRegex,
816                 new File("."),
817                 fileRegex,
818                 encoding);
819         return results;
820     }
821 
822     /**
823      * Sed implementation for a single file.
824      * <p>
825      * Oginal source code from http://kickjava.com/src/org/apache/lenya/util/SED.java.htm.
826      * <p>
827      * May fail on windows with error :
828      * The requested operation cannot be performed on a file with a user-mapped section open
829      *
830      * @param searchRegex Prefix which shall be replaced
831      * @param replace     Prefix which is going to replace the original
832      * @param file        File which sed shall be applied
833      * @param encoding    charset encoding
834      * @throws IOException FIXME
835      * @since 1.1.2
836      */
837     public static void sed(String searchRegex,
838                            String replace,
839                            File file,
840                            String encoding) throws IOException {
841 
842         Pattern pattern = Pattern.compile(searchRegex);
843 
844         String outString = null;
845         FileInputStream fis = new FileInputStream(file);
846         try {
847             // Open the file and then get a channel from the stream
848             FileChannel fc = fis.getChannel();
849 
850             // Get the file's size and then map it into memory
851             int sz = (int) fc.size();
852             MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
853 
854             // Decode the file into a char buffer
855             // Charset and decoder for encoding
856             Charset charset = Charset.forName(encoding);
857             CharsetDecoder decoder = charset.newDecoder();
858             CharBuffer cb = decoder.decode(bb);
859 
860             Matcher matcher = pattern.matcher(cb);
861             outString = matcher.replaceAll(replace);
862         } finally {
863             fis.close();
864         }
865 
866         if (outString != null) {
867             PrintStream ps = new PrintStream(new FileOutputStream(file));
868             try {
869                 ps.print(outString);
870             } finally {
871                 ps.close();
872             }
873         }
874     }
875 
876     /**
877      * Java implementation for the unix sed command.
878      * <p>
879      * May fail on windows with error :
880      * The requested operation cannot be performed on a file with a user-mapped section open
881      *
882      * @param searchRegex   regex to search into file
883      * @param replace       string to replace matching patterns
884      * @param rootDirectory directory to search into
885      * @param fileRegex     regex for file to find in {@code rootDirectory}
886      * @param encoding      encoding to use
887      * @throws IOException FIXME
888      * @since 1.1.2
889      */
890     public static void sed(String searchRegex,
891                            String replace,
892                            File rootDirectory,
893                            String fileRegex,
894                            String encoding) throws IOException {
895         List<File> files = find(rootDirectory, fileRegex, true);
896         for (File file : files) {
897             sed(searchRegex, replace, file, encoding);
898         }
899     }
900 
901     /**
902      * Java implementation for the unix sed command.
903      * <p>
904      * May fail on windows with error :
905      * The requested operation cannot be performed on a file with a user-mapped section open
906      *
907      * @param searchRegex regex to search into file
908      * @param replace     string to replace matching patterns
909      * @param fileRegex   regex for file to find in current dir
910      * @param encoding    encoding to use
911      * @throws IOException FIXME
912      * @since 1.1.2
913      */
914     public static void sed(String searchRegex,
915                            String replace,
916                            String fileRegex,
917                            String encoding) throws IOException {
918         sed(searchRegex, replace, new File("."), fileRegex, encoding);
919     }
920 
921     /**
922      * Create the directory (and his parents) if necessary.
923      *
924      * @param dir the directory to create if not exisiting
925      * @return {@code true} if directory was created, {@code false} if was no
926      * need to create it
927      * @since 1.3.2
928      */
929     public static boolean createDirectoryIfNecessary(File dir) {
930         if (!dir.exists()) {
931             // do not throw exception if directory was created by another thread
932             return dir.mkdirs();
933         }
934         return false;
935     }
936 
937     /**
938      * Obtain a file from the given {@code rootDirectory}, applying given paths.
939      * <p>
940      * For example with paths = a, b and c, then result is :
941      * <pre>
942      * root/a/b/c
943      * </pre>
944      *
945      * @param rootDirectory the root directory
946      * @param paths         paths to apply
947      * @return the final file
948      * @since 2.2
949      */
950     public static File getFileFromPaths(File rootDirectory, String... paths) {
951         File result = rootDirectory;
952         for (String path : paths) {
953             result = new File(result, path);
954         }
955         return result;
956     }
957 
958     /**
959      * Obtain a file fro the given {@code rootDirectory}, applying the fqn.
960      * <p>
961      * For example with fqn = a.b.c, the result is :
962      * <pre>
963      * root/a/b/c
964      * </pre>
965      *
966      * @param rootDirectory the root directory
967      * @param fqn           fqn of searched file
968      * @return the final file
969      * @since 2.2
970      */
971     public static File getFileFromFQN(File rootDirectory, String fqn) {
972         String[] paths = fqn.split("\\.");
973 
974         File result = getFileFromPaths(rootDirectory, paths);
975         return result;
976     }
977 
978     /**
979      * Obtain a directory and creates it if required to place some test data.
980      * <p>
981      * The directory will be :
982      * <pre>
983      *     java.io.tmpdir/testclassName.fqn/methodName[/classifier]/timestamp
984      * </pre>
985      *
986      * @param testClassName test class name
987      * @param methodName    method name
988      * @param classifier    optional classifier
989      * @param timestamp     timestamp
990      * @return the computed and created if required directory.
991      * @since 2.6.10
992      */
993     public static File getTestSpecificDirectory(Class<?> testClassName,
994                                                 String methodName,
995                                                 String classifier,
996                                                 long timestamp) {
997         File tempDirFile = SystemUtils.getJavaIoTmpDir();
998 
999         // create the directory to store database data
1000         String dataBasePath = testClassName.getName()
1001                 + File.separator // a directory with the test class name
1002                 + methodName; // a sub-directory with the method name
1003 
1004         if (StringUtils.isNotBlank(classifier)) {
1005             dataBasePath += classifier;
1006         }
1007         dataBasePath += '_'
1008                 + timestamp; // and a timestamp
1009         File databaseFile = new File(tempDirFile, dataBasePath);
1010         return databaseFile;
1011     }
1012 
1013     /**
1014      * Tests if a file is GZipped.
1015      *
1016      * @param file file to test
1017      * @return {@code true} if file is gzipped, {@code false} otherwise
1018      * @throws IOException if any io errors while reading file
1019      * @since 3.0
1020      */
1021     public static boolean isGzipFile(File file) throws IOException {
1022 
1023         InputStream in = new BufferedInputStream(new FileInputStream(file));
1024 
1025         try {
1026             boolean gzip = GZUtil.isGzipStream(in);
1027             in.close();
1028             return gzip;
1029         } finally {
1030             IOUtils.closeQuietly(in);
1031         }
1032 
1033     }
1034 
1035 } // FileUtil