001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *     Bogdan Stefanescu <bs@nuxeo.com>
019 *     Estelle Giuly <egiuly@nuxeo.com>
020 */
021package org.nuxeo.common.utils;
022
023import java.io.BufferedOutputStream;
024import java.io.Closeable;
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.FileOutputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.OutputStream;
031import java.io.UnsupportedEncodingException;
032import java.net.MalformedURLException;
033import java.net.URISyntaxException;
034import java.net.URL;
035import java.net.URLDecoder;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.List;
039import java.util.zip.ZipInputStream;
040
041import org.apache.commons.io.Charsets;
042import org.apache.commons.io.IOUtils;
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045
046public final class FileUtils {
047
048    private static final Log log = LogFactory.getLog(FileUtils.class);
049
050    // This is an utility class
051    private FileUtils() {
052    }
053
054    /**
055     * @deprecated since 9.1 seems unused
056     */
057    @Deprecated
058    public static void safeClose(Closeable stream) {
059        try {
060            stream.close();
061        } catch (IOException e) {
062            // do nothing
063        }
064    }
065
066    /**
067     * @deprecated since 9.1 use {@link IOUtils#copy(InputStream, OutputStream)} instead.
068     */
069    @Deprecated
070    public static void copy(InputStream in, OutputStream out) throws IOException {
071        IOUtils.copy(in, out);
072    }
073
074    /**
075     * @deprecated since 9.1 use {@link IOUtils#toByteArray(URL)} instead.
076     */
077    @Deprecated
078    public static byte[] readBytes(URL url) throws IOException {
079        return IOUtils.toByteArray(url);
080    }
081
082    /**
083     * @deprecated since 9.1 use {@link IOUtils#toByteArray(InputStream)} instead.
084     */
085    @Deprecated
086    public static byte[] readBytes(InputStream in) throws IOException {
087        return IOUtils.toByteArray(in);
088    }
089
090    /**
091     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#readLines(File)} instead.
092     */
093    @Deprecated
094    public static String readFile(File file) throws IOException {
095        return org.apache.commons.io.FileUtils.readFileToString(file, Charsets.UTF_8);
096    }
097
098    /**
099     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#readLines(File)} instead.
100     */
101    @Deprecated
102    public static List<String> readLines(File file) throws IOException {
103        return org.apache.commons.io.FileUtils.readLines(file);
104    }
105
106    /**
107     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#writeLines(File, Collection)} instead.
108     */
109    @Deprecated
110    public static void writeLines(File file, List<String> lines) throws IOException {
111        org.apache.commons.io.FileUtils.writeLines(file, lines);
112    }
113
114    /**
115     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#readFileToByteArray(File)} instead.
116     */
117    @Deprecated
118    public static byte[] readBytes(File file) throws IOException {
119        return org.apache.commons.io.FileUtils.readFileToByteArray(file);
120    }
121
122    /**
123     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#writeByteArrayToFile(File, byte[])} instead.
124     */
125    @Deprecated
126    public static void writeFile(File file, byte[] buf) throws IOException {
127        org.apache.commons.io.FileUtils.writeByteArrayToFile(file, buf);
128    }
129
130    /**
131     * @since 5.5
132     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#writeByteArrayToFile(File, byte[], boolean)}
133     *             instead.
134     */
135    @Deprecated
136    public static void writeFile(File file, byte[] buf, boolean append) throws IOException {
137        org.apache.commons.io.FileUtils.writeByteArrayToFile(file, buf, append);
138    }
139
140    /**
141     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#writeStringToFile(File, String)} instead.
142     */
143    @Deprecated
144    public static void writeFile(File file, String buf) throws IOException {
145        org.apache.commons.io.FileUtils.writeStringToFile(file, buf);
146    }
147
148    /**
149     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#writeStringToFile(File, String, boolean)}
150     *             instead.
151     * @since 5.5
152     */
153    @Deprecated
154    public static void writeFile(File file, String buf, boolean append) throws IOException {
155        org.apache.commons.io.FileUtils.writeStringToFile(file, buf, append);
156    }
157
158    /**
159     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#copyURLToFile(URL, File)} instead.
160     */
161    @Deprecated
162    public static void download(URL url, File file) throws IOException {
163        org.apache.commons.io.FileUtils.copyURLToFile(url, file);
164    }
165
166    /**
167     * @deprecated since 9.1 Use {@link org.apache.commons.io.FileUtils#copyInputStreamToFile(InputStream, File)}
168     *             instead. <strong>Note:</strong> that proposed method close the stream, could lead to issues with some
169     *             input stream (like {@link ZipInputStream} which uses a global cursor on zip file stream when
170     *             iterating on files in it).
171     */
172    @Deprecated
173    public static void copyToFile(InputStream in, File file) throws IOException {
174        org.apache.commons.io.FileUtils.copyInputStreamToFile(in, file);
175    }
176
177    /**
178     * @deprecated since 9.1 Use {@link IOUtils#copy(InputStream, OutputStream)} instead. See
179     *             {@link #append(InputStream, File, boolean)} for more information.
180     */
181    @Deprecated
182    public static void append(File src, File dst) throws IOException {
183        append(src, dst, false);
184    }
185
186    /**
187     * @deprecated since 9.1 Use {@link IOUtils#copy(InputStream, OutputStream)} instead. See
188     *             {@link #append(InputStream, File, boolean)} for more information.
189     */
190    @Deprecated
191    public static void append(File src, File dst, boolean appendNewLine) throws IOException {
192        try (InputStream in = new FileInputStream(src)) {
193            append(in, dst, appendNewLine);
194        }
195    }
196
197    /**
198     * @deprecated since 9.1 Use {@link IOUtils#copy(InputStream, OutputStream)} instead. See
199     *             {@link #append(InputStream, File, boolean)} for more information.
200     */
201    @Deprecated
202    public static void append(InputStream in, File file) throws IOException {
203        append(in, file, false);
204    }
205
206    /**
207     * @deprecated since 9.1 Use {@link IOUtils#copy(InputStream, OutputStream)} instead.
208     */
209    @Deprecated
210    public static void append(InputStream in, File file, boolean appendNewLine) throws IOException {
211        try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) {
212            if (appendNewLine) {
213                out.write(System.getProperty("line.separator").getBytes());
214            }
215            IOUtils.copy(in, out);
216        }
217    }
218
219    /**
220     * Copies source to destination. If source and destination are the same, does nothing. Both single files and
221     * directories are handled.
222     *
223     * @param src the source file or directory
224     * @param dst the destination file or directory
225     * @throws IOException
226     */
227    public static void copy(File src, File dst) throws IOException {
228        if (src.equals(dst)) {
229            return;
230        }
231        if (src.isFile()) {
232            copyFile(src, dst);
233        } else {
234            copyTree(src, dst);
235        }
236    }
237
238    public static void copy(File[] src, File dst) throws IOException {
239        for (File file : src) {
240            copy(file, dst);
241        }
242    }
243
244    public static void copyFile(File src, File dst) throws IOException {
245        if (dst.isDirectory()) {
246            dst = new File(dst, src.getName());
247        }
248        org.apache.commons.io.FileUtils.copyFile(src, dst, false);
249    }
250
251    /**
252     * Copies recursively source to destination.
253     * <p>
254     * The source file is assumed to be a directory.
255     *
256     * @param src the source directory
257     * @param dst the destination directory
258     * @throws IOException
259     */
260    public static void copyTree(File src, File dst) throws IOException {
261        if (src.isFile()) {
262            copyFile(src, dst);
263        } else if (src.isDirectory()) {
264            if (dst.exists()) {
265                dst = new File(dst, src.getName());
266                dst.mkdir();
267            } else { // allows renaming dest dir
268                dst.mkdirs();
269            }
270            File[] files = src.listFiles();
271            for (File file : files) {
272                copyTree(file, dst);
273            }
274        }
275    }
276
277    public static void copyTree(File src, File dst, PathFilter filter) throws IOException {
278        copyTree(src, dst, new Path("/"), filter);
279    }
280
281    public static void copyTree(File src, File dst, Path prefix, PathFilter filter) throws IOException {
282        if (!prefix.isAbsolute()) {
283            prefix = prefix.makeAbsolute();
284        }
285        int rootIndex = src.getPath().length() + 1;
286        for (File file : src.listFiles()) {
287            copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter);
288        }
289    }
290
291    protected static void copyTree(int rootIndex, File src, File dst, Path prefix, PathFilter filter)
292            throws IOException {
293        if (src.isFile()) {
294            String relPath = src.getPath().substring(rootIndex);
295            if (!filter.accept(new Path(relPath))) {
296                return;
297            }
298            if (!prefix.isRoot()) { // remove prefix from path
299                String path = dst.getPath();
300                String pff = prefix.toString();
301                int prefixIndex = path.lastIndexOf(pff);
302                if (prefixIndex > 0) {
303                    path = path.substring(0, prefixIndex) + path.substring(prefixIndex + pff.length());
304                    dst = new File(path.toString());
305                }
306            }
307            dst.getParentFile().mkdirs();
308            copyFile(src, dst);
309        } else if (src.isDirectory()) {
310            File[] files = src.listFiles();
311            for (File file : files) {
312                copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter);
313            }
314        }
315    }
316
317    /**
318     * Decodes an URL path so that is can be processed as a filename later.
319     *
320     * @param url the Url to be processed.
321     * @return the decoded path.
322     */
323    public static String getFilePathFromUrl(URL url) {
324        String path = "";
325        if (url.getProtocol().equals("file")) {
326            try {
327                path = URLDecoder.decode(url.getPath(), "UTF-8");
328            } catch (UnsupportedEncodingException e) {
329                log.error(e);
330            }
331        }
332        return path;
333    }
334
335    public static File getFileFromURL(URL url) {
336        File file;
337        String filename = getFilePathFromUrl(url);
338        if (filename.equals("")) {
339            file = null;
340        } else {
341            file = new File(filename);
342        }
343        return file;
344    }
345
346    public static String getParentPath(String path) {
347        int p = path.lastIndexOf(File.separator);
348        if (p == -1) {
349            return null;
350        }
351        return path.substring(0, p);
352    }
353
354    public static String getFileName(String path) {
355        int p = path.lastIndexOf(File.separator);
356        if (p == -1) {
357            return path;
358        }
359        return path.substring(p + 1);
360    }
361
362    public static String getFileExtension(String path) {
363        int p = path.lastIndexOf('.');
364        if (p == -1) {
365            return null;
366        }
367        return path.substring(p + 1);
368    }
369
370    public static String getFileNameNoExt(String path) {
371        String name = getFileName(path);
372        int p = name.lastIndexOf('.');
373        if (p == -1) {
374            return name;
375        }
376        return name.substring(0, p);
377    }
378
379    /**
380     * Retrieves the total path of a resource from the Thread Context.
381     *
382     * @param resource the resource name to be retrieved.
383     * @return the decoded path.
384     */
385    public static String getResourcePathFromContext(String resource) {
386        URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
387        return getFilePathFromUrl(url);
388    }
389
390    public static File getResourceFileFromContext(String resource) {
391        File file;
392        String filename = getResourcePathFromContext(resource);
393        if (filename.equals("")) {
394            file = null;
395        } else {
396            file = new File(filename);
397        }
398        return file;
399    }
400
401    public static File[] findFiles(File root, String pattern, boolean recurse) {
402        List<File> result = new ArrayList<>();
403        if (pattern == null) {
404            if (recurse) {
405                collectFiles(root, result);
406            } else {
407                return root.listFiles();
408            }
409        } else {
410            FileNamePattern pat = new FileNamePattern(pattern);
411            if (recurse) {
412                collectFiles(root, pat, result);
413            } else {
414                File[] files = root.listFiles();
415                for (File file : files) {
416                    if (pat.match(file.getName())) {
417                        result.add(file);
418                    }
419                }
420            }
421        }
422        return result.toArray(new File[result.size()]);
423    }
424
425    public static void collectFiles(File root, FileNamePattern pattern, List<File> result) {
426        File[] files = root.listFiles();
427        for (File file : files) {
428            if (pattern.match(file.getName())) {
429                result.add(file);
430                if (file.isDirectory()) {
431                    collectFiles(file, pattern, result);
432                }
433            }
434        }
435    }
436
437    public static void collectFiles(File root, List<File> result) {
438        File[] files = root.listFiles();
439        for (File file : files) {
440            result.add(file);
441            if (file.isDirectory()) {
442                collectFiles(file, result);
443            }
444        }
445    }
446
447    /**
448     * @deprecated since 9.1 Use {@link IOUtils#closeQuietly(InputStream)} instead or {@link AutoCloseable} feature.
449     */
450    @Deprecated
451    public static void close(InputStream in) {
452        if (in != null) {
453            try {
454                in.close();
455            } catch (IOException e) {
456                log.error(e);
457            }
458        }
459    }
460
461    /**
462     * @deprecated since 9.1 Use {@link IOUtils#closeQuietly(InputStream)} instead or {@link AutoCloseable} feature.
463     */
464    @Deprecated
465    public static void close(OutputStream out) {
466        if (out != null) {
467            try {
468                out.close();
469            } catch (IOException e) {
470                log.error(e);
471            }
472        }
473    }
474
475    /**
476     * Create a file handler (this doesn't create a real file) given a file URI. This method can be used to create files
477     * from invalid URL strings (e.g. containing spaces ..)
478     *
479     * @return a file object
480     */
481    public static File urlToFile(String url) throws MalformedURLException {
482        return urlToFile(new URL(url));
483    }
484
485    public static File urlToFile(URL url) {
486        try {
487            return new File(url.toURI());
488        } catch (URISyntaxException e) {
489            return new File(url.getPath());
490        }
491    }
492
493    /**
494     * @deprecated since 9.1 Use {@link IOUtils#readLines(InputStream)} instead.
495     */
496    @Deprecated
497    public static List<String> readLines(InputStream in) throws IOException {
498        return IOUtils.readLines(in);
499    }
500
501    /**
502     * Compares two files content as String even if their EOL are different
503     *
504     * @param expected a file content with Windows or Unix like EOL
505     * @param source another file content with Windows or Unix like EOL
506     * @return the result of equals after replacing their EOL
507     */
508    public static boolean areFilesContentEquals(String expected, String source) {
509        if (expected == source) {
510            return true;
511        }
512
513        if (expected == null || source == null) {
514            return false;
515        }
516
517        if (expected.length() != source.length()) {
518            // Prevent from comparing files with Windows EOL
519            return expected.replace("\r\n", "\n").equals(source.replace("\r\n", "\n"));
520        } else {
521            return expected.equals(source);
522        }
523    }
524
525    /**
526     * Returns a safe filename, replacing unsafe characters (: \ / * ..) with "_". For instance, it turns
527     * "tmp/../2349:876398/foo.png" into "tmp___2349_876398_foo.png"
528     *
529     * @param filename the filename
530     * @return the safe filename with underscores instead of unsafe characters
531     * @since 9.1
532     */
533    public static String getSafeFilename(String filename) {
534        return filename.replaceAll("(\\\\)|(\\/)|(\\:)|(\\*)|(\\.\\.)", "_");
535    }
536
537}