View Javadoc

1   /*
2    * $Id: GHFileUtils.java 2993 2011-11-24 19:51:48Z andrewinkler $
3    * ============================================================================
4    * Project gluehloch-homepage-core
5    * Copyright (c) 2004-2007 by Andre Winkler. All rights reserved.
6    * ============================================================================
7    *          GNU LESSER GENERAL PUBLIC LICENSE
8    *  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
9    *
10   *  This library is free software; you can redistribute it and/or
11   *  modify it under the terms of the GNU Lesser General Public
12   *  License as published by the Free Software Foundation; either
13   *  version 2.1 of the License, or (at your option) any later version.
14   *
15   *  This library is distributed in the hope that it will be useful,
16   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18   *  Lesser General Public License for more details.
19   *
20   *  You should have received a copy of the GNU Lesser General Public
21   *  License along with this library; if not, write to the Free Software
22   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23   *
24   */
25  
26  package de.awtools.homegen.directory.utils;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.Comparator;
34  import java.util.List;
35  import java.util.Map;
36  
37  import org.apache.commons.io.FileUtils;
38  import org.apache.commons.io.filefilter.SuffixFileFilter;
39  import org.apache.commons.lang.StringUtils;
40  
41  import de.awtools.basic.file.AWToolsFileUtils;
42  import de.awtools.homegen.TransformerException;
43  import de.awtools.homegen.directory.GHBuilder;
44  import de.awtools.homegen.directory.GHDirectory;
45  import de.awtools.homegen.directory.GHFile;
46  
47  /**
48   * Verschiedene Utilities für das Suchen von Dateien in Verzeichnissen etc.
49   *
50   * @version $LastChangedRevision: 2993 $ $LastChangedDate: 2011-11-24 20:51:48 +0100 (Thu, 24 Nov 2011) $
51   * @author by Andre Winkler, $LastChangedBy: andrewinkler $
52   */
53  public class GHFileUtils {
54  
55      /**
56       * Prüft, ob der übergebene File-Parameter ein Verzeichnis und lesbar ist.
57       * 
58       * @param dir Das zu prüfende <code>File</code> Objekt.
59       */
60      public static void checkDirectory(final File dir) {
61          if (!dir.canRead()) {
62              throw new TransformerException(dir.getPath() + " is not readable!");
63          }
64          if (!dir.isDirectory()) {
65              throw new TransformerException(dir.getPath()
66                      + " is not a directory!");
67          }
68      }
69  
70      /**
71       * Prüft, ob der übergebene File-Parameter eine Datei und lesbar ist.
72       * 
73       * @param file Das zu prüfende <code>File</code> Objekt.
74       */
75      public static void checkFile(final File file) {
76          if (!file.canRead()) {
77              throw new TransformerException(file.getPath() + " is not readable!");
78          }
79          if (file.isDirectory()) {
80              throw new TransformerException(file.getPath() + " is a directory!");
81          }
82      }
83  
84      /**
85       * Sucht nach einem <code>file</code> in dem Verzeichnis
86       * <code>directory</code>. Das übergebene und das eventuell gefundenen
87       * {@link GHFile} Objekt müssen nicht im Sinne von <code>==</code> gleich
88       * sein. Diese Methode ist sinnvoll bei Verwendung von zwei
89       * unterschiedlichen Root-Verzeichnissen, die eine ähnliche Unterstruktur
90       * besitzen.
91       *
92       * @param file Die zu suchende Datei.
93       * @param directory Das Verzeichnis und deren Unterverzeichnisse die
94       *     untersucht werden.
95       * @return Die gefundene Datei oder <code>null</code> wenn nichts gefunden.
96       */
97      public static GHFile findFile(final GHFile file, final GHDirectory directory) {
98          GHFile foundedFile = null;
99          List<GHFile> files = directory.getAllFiles();
100         int index = files.indexOf(file);
101         if (index != -1) {
102             foundedFile = files.get(index);
103         }
104         return foundedFile;
105     }
106 
107     /**
108      * Siehe dazu in der Methode {@link #findFile(GHFile, GHDirectory)}. Die
109      * Datei-Extension wird bei der Suche ignoriert. Die Dateien müssen nur
110      * gleich im Namen sein.
111      *
112      * @param file Die zu suchende Datei.
113      * @param directory Das Verzeichnis und deren Unterverzeichnisse die
114      *     untersucht werden.
115      * @param expectedExtension Die erwartete Erweiterung.
116      * @return Die gefundene Datei oder <code>null</code> wenn nichts gefunden.
117      */
118     public static GHFile findFileIgnoreExtension(final GHFile file,
119             final GHDirectory directory, final String expectedExtension) {
120 
121         GHFile foundedFile = null;
122         List<GHFile> files = directory.getAllFiles();
123         for (GHFile currFile : files) {
124             if (currFile.getNameWithoutExtension().equals(
125                     file.getNameWithoutExtension())
126                     && currFile.getExtension().equals(expectedExtension)) {
127 
128                 foundedFile = currFile;
129                 break;
130             }
131         }
132         return foundedFile;
133     }
134 
135     /**
136      * Kopiert eine Datei <code>file</code> in das Verzeichnis
137      * <code>directory</code>. Den relativen Pfad den die Datei
138      * <code>file</code> im alten {@link GHDirectory} besaß, wird sie im
139      * neuen <code>director</code> beibehalten.
140      *
141      * @param file Die zu kopierende Datei.
142      * @param target Das Verzeichnis im dem die Datei kopiert werden soll.
143      * @param force Wenn <code>false</code> wird die Kopieraktion nur dann
144      *     ausgeführt, wenn im Target-Verzeichnis die Datei noch nicht vorhanden
145      *     ist, oder die zu kopierende Datei ein jüngeres Datum als die
146      *     Datei im Target-Verzeichnis besitzt.
147      * @param builder Erstellt das passende {@link GHDirectory} Objekt.
148      * @return Liefert <code>true</code> zurück, wenn die Datei kopiert wurde.
149      *     In allen anderen Fällen wird <code>false</code> zurück geliefert.
150      */
151     public static boolean copyFile(final GHFile file, final GHDirectory target,
152             final boolean force, final GHBuilder builder) {
153 
154         boolean result = false;
155         List<GHDirectory> path = file.getDirectoryPath();
156         GHDirectory targetDirectory = GHFileUtils.createPath(path, target,
157                 builder);
158 
159         GHFile targetFile = targetDirectory.getFile(file.getName());
160         if (targetFile == null) {
161             try {
162                 FileUtils.copyFileToDirectory(file.getFile(),
163                         targetDirectory.getFile());
164             } catch (IOException ex) {
165                 throw new TransformerException(ex);
166             }
167             GHFile newFile = builder.buildFile(
168                     new File(targetDirectory.getFile(), file.getName()),
169                     targetDirectory, file.getName());
170             targetDirectory.addFile(newFile);
171             result = true;
172         } else if (file.isNewer(targetFile) || force) {
173             try {
174                 FileUtils.copyFile(file.getFile(), targetFile.getFile());
175             } catch (IOException ex) {
176                 throw new TransformerException(ex);
177             }
178             result = true;
179         }
180 
181         return result;
182     }
183 
184     /**
185      * Erstellt den übergebenen Verzeichnispfad im Target-Verzeichnis. Die
186      * etwaigen Dateien die im übergebenen Verzeichnispfad hängen, werden nicht
187      * angelegt.
188      *
189      * @param path Der anzulegende Pfad.
190      * @param target Das Target-Root-Verzeichnis. In diesem werden ggf. die
191      *     fehlenden Pfade nachgetragen.
192      * @param builder Erstellt das passende {@link GHDirectory} Objekt.
193      * @return Das letzte angelegte Verzeichnis.
194      */
195     public static GHDirectory createPath(final List<GHDirectory> path,
196             final GHDirectory target, final GHBuilder builder) {
197 
198         // Das erste Verzeichnis in der Liste muss das Wurzelverzeichnis sein.
199         if (!path.get(0).isRootDir()) {
200             throw new IllegalArgumentException(
201                     "path must start with a root dir!");
202         }
203 
204         // Wenn die Liste nur aus einem Element besteht, dann können wir
205         // bereits jetzt aufhören. Das Root-Verzeichnis existiert auch
206         // im Target-Verzeichnis immer!
207         if (path.size() < 2) {
208             return target;
209         }
210 
211         GHDirectory currentDir = target;
212         for (int index = 1; index < path.size(); index++) {
213             String dirName = path.get(index).getName();
214             GHDirectory dir = currentDir.getDirectory(dirName);
215             if (dir == null) {
216                 File newDir = new File(currentDir.getFile(), dirName);
217                 newDir.mkdir();
218                 dir = builder.buildDirectory(currentDir, newDir, dirName);
219             }
220             currentDir = dir;
221         }
222 
223         return currentDir;
224     }
225 
226     // -- Helper Methods ------------------------------------------------------
227 
228     /**
229      * Entfernt aus einem Dateibezeichner den Basispfad. Zum Beispiel:
230      * Basis=<code>C:\temp\test</code> und
231      * Dateiname=<code>C:\temp\test\dir\subdir\test.txt</code>. Dann
232      * liefert die Methode <code>dir/subdir/test.txt</code> zurück.
233      *
234      * @param baseDir Das Basisverzeichnis.
235      * @param fileName Die Datei.
236      * @return Ein relative Pfad zum Basisverzeichnis.
237      *
238      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
239      */
240     public static String removeBaseDir(final File baseDir, final File fileName) {
241         try {
242             return (removeBaseDir(baseDir.getCanonicalPath(),
243                     fileName.getCanonicalPath()));
244         } catch (IOException ex) {
245             throw new TransformerException(ex);
246         }
247     }
248 
249     /**
250      * Entfernt aus einem Dateibezeichner den Basispfad. Zum Beispiel:
251      * Basis=<code>C:\temp\test</code> und
252      * Dateiname=<code>C:\temp\test\dir\subdir\test.txt</code>. Dann
253      * liefert die Methode <code>dir/subdir/test.txt</code> zurück.<br>
254      * <strong>HINWEIS:</strong> Die Pfad- und Dateiangaben müssen in
255      * kanonischer Form vorliegen. Das Endergebnis der Operation wird
256      * normalisiert! Siehe dazu
257      * {@link WinFileUtils#normalizePath(java.lang.String)}.
258      *
259      * @param baseDir Das Basisverzeichnis.
260      * @param fileName Die Datei.
261      * @return Ein relativer Pfad zum Basisverzeichnis.
262      *
263      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
264      */
265     public static String removeBaseDir(final String baseDir,
266             final String fileName) {
267 
268         String right = StringUtils.substringAfterLast(fileName, baseDir);
269         return (AWToolsFileUtils.normalizePath(right));
270     }
271 
272     /**
273      * Liefert eine Liste von Files aus dem übergebenen Verzeichnis.
274      *
275      * @param directory Das zu untersuchende Verzeichnis.
276      * @param suffixFileFilter Ein Filter. Nur diese Dateien landen in der
277      *     Ergebnisliste.
278      * @return Die Files in diesem Verzeichnis.
279      *
280      * @see #findFiles(File)
281      */
282     @SuppressWarnings("unchecked")
283     public static List<File> findFiles(final File directory,
284             final SuffixFileFilter suffixFileFilter) {
285 
286         List<File> results = new ArrayList<File>();
287         Collection<File> files = FileUtils.listFiles(directory,
288                 suffixFileFilter, null);
289 
290         for (File file : files) {
291             if (file.isFile()) {
292                 results.add(file);
293             }
294         }
295 
296         sortFiles(results);
297         return results;
298     }
299 
300     /**
301      * Liefert eine Liste von Files aus dem übergebenen Verzeichnis.
302      *
303      * @param directory Das Ausgangsverzeichnis.
304      * @return Die gefundenen Dateien.
305      *
306      * @see #findFiles(File, SuffixFileFilter)
307      *
308      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
309      */
310     public static List<File> findFiles(final File directory) {
311         List<File> results = new ArrayList<File>();
312         File[] files = directory.listFiles();
313         if (files != null) {
314             for (int index = 0; index < files.length; index++) {
315                 File file = files[index];
316                 if (file.isFile()) {
317                     results.add(file);
318                 }
319             }
320         }
321 
322         sortFiles(results);
323         return results;
324     }
325 
326     /**
327      * Wird von der Methode {@link #findDeepDirectories(File)} verwendet und
328      * ermittelt alle Verzeichnisse unterhalb des gesuchten Verzeichnisses.
329      *
330      * @param directory Das Ausgangsverzeichnis.
331      * @return Die gefundenen Unterverzeichnisse.
332      *
333      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
334      */
335     public static List<File> findDirectories(final File directory) {
336         List<File> results = new ArrayList<File>();
337         File[] files = directory.listFiles();
338         if (files != null) {
339             for (int index = 0; index < files.length; index++) {
340                 File file = files[index];
341                 if (file.isDirectory()) {
342                     results.add(file);
343                 }
344             }
345         }
346 
347         sortFiles(results);
348         return results;
349     }
350 
351     private static void sortFiles(List<File> files) {
352         Collections.sort(files, new Comparator<File>() {
353             @Override
354             public int compare(File file1, File file2) {
355                 return file1.getName().compareTo(file2.getName());
356             }            
357         });        
358     }
359 
360     /**
361      * Fischt aus einem Verzeichnis alle Unterverzeichnisse heraus.
362      *
363      * @param directory Das zu untersuchende Verzeichnis.
364      * @return Die gefundenen Unterverzeichnisse.
365      *
366      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
367      */
368     public static List<File> findDeepDirectories(final File directory) {
369         List<File> directoryFiles = new ArrayList<File>();
370         findDeepDirectories(directoryFiles, directory);
371         return directoryFiles;
372     }
373 
374     /**
375      * Wird von der Methode {@link #findDeepDirectories(File)} verwendet und
376      * verwendet einen rekursiven Abstieg zur Ermittlung aller Verzeichnisse.
377      *
378      * @param results Die ermittelten Unterverzeichnisse.
379      * @param directory Das Ausgangsverzeichnis.
380      *
381      * @todo Eventuell nach winutils in die Klasse FileUtils auslagern.
382      */
383     private static void findDeepDirectories(final List<File> results,
384             final File directory) {
385 
386         File[] files = directory.listFiles();
387         for (int index = 0; index < files.length; index++) {
388             File file = files[index];
389             if (file.isDirectory()) {
390                 results.add(file);
391                 findDeepDirectories(results, file);
392             }
393         }
394     }
395 
396     /**
397      * Ersetzt die Tokens '@...@' in der übergebenen Datei. Das Datum
398      * 'lastModified' wird dabei nicht verändert. Nach der Filter-Operations
399      * wird dieses wieder auf den alten Wert gesetzt.
400      *
401      * @param source Die zu untersuchende Datei.
402      * @param tokens Die Tokens.
403      * @param encoding Sollte i.d.R. UTF-8 oder ISO-8859-1 sein.
404      */
405     public static void replaceInFile(final File source,
406             final Map<String, String> tokens, final String encoding) {
407 
408         try {
409             long lastModified = source.lastModified();
410             String text = FileUtils.readFileToString(source, encoding);
411             String result = GHFileUtils.replace(text, tokens, encoding);
412             FileUtils.writeStringToFile(source, result, encoding);
413             source.setLastModified(lastModified);
414         } catch (IOException ex) {
415             throw new TransformerException(ex);
416         }
417     }
418 
419     /**
420      * Ersetzt alle @...@ Ausdrücke durch die entsprechenden Werte.
421      *
422      * @param text Der Text
423      * @param tokens Die Tokens mit der Ersetzung.
424      * @param encoding Sollte i.d.R. UTF-8 oder ISO-8859-1 sein.
425      * @return Das Endergebnis.
426      */
427     public static String replace(final String text,
428             final Map<String, String> tokens, final String encoding) {
429 
430         String result = text;
431         for (String key : tokens.keySet()) {
432             String value = tokens.get(key);
433             StringBuilder replaceToken = new StringBuilder("@");
434             replaceToken.append(key).append("@");
435             result = StringUtils
436                     .replace(result, replaceToken.toString(), value);
437         }
438         return result;
439     }
440 
441 }