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.File;
024import java.io.IOException;
025import java.io.UnsupportedEncodingException;
026import java.net.MalformedURLException;
027import java.net.URISyntaxException;
028import java.net.URL;
029import java.net.URLDecoder;
030import java.util.ArrayList;
031import java.util.List;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036public final class FileUtils {
037
038    private static final Log log = LogFactory.getLog(FileUtils.class);
039
040    // This is an utility class
041    private FileUtils() {
042    }
043
044    /**
045     * Copies source to destination. If source and destination are the same, does nothing. Both single files and
046     * directories are handled.
047     *
048     * @param src the source file or directory
049     * @param dst the destination file or directory
050     */
051    public static void copy(File src, File dst) throws IOException {
052        if (src.equals(dst)) {
053            return;
054        }
055        if (src.isFile()) {
056            copyFile(src, dst);
057        } else {
058            copyTree(src, dst);
059        }
060    }
061
062    public static void copy(File[] src, File dst) throws IOException {
063        for (File file : src) {
064            copy(file, dst);
065        }
066    }
067
068    /**
069     * @deprecated since 10.1 - use {@link org.apache.commons.io.FileUtils#copyFile(File, File)} or
070     *             {@link org.apache.commons.io.FileUtils#copyFileToDirectory(File, File)} instead.
071     */
072    @Deprecated
073    public static void copyFile(File src, File dst) throws IOException {
074        if (dst.isDirectory()) {
075            dst = new File(dst, src.getName());
076        }
077        org.apache.commons.io.FileUtils.copyFile(src, dst, false);
078    }
079
080    /**
081     * Copies recursively source to destination.
082     * <p>
083     * The source file is assumed to be a directory.
084     *
085     * @param src the source directory
086     * @param dst the destination directory
087     * @deprecated since 10.1 - waiting ReloadComponent to be cleaned
088     */
089    @Deprecated
090    public static void copyTree(File src, File dst) throws IOException {
091        if (src.isFile()) {
092            copyFile(src, dst);
093        } else if (src.isDirectory()) {
094            if (dst.exists()) {
095                dst = new File(dst, src.getName());
096                dst.mkdir();
097            } else { // allows renaming dest dir
098                dst.mkdirs();
099            }
100            File[] files = src.listFiles();
101            for (File file : files) {
102                copyTree(file, dst);
103            }
104        }
105    }
106
107    /**
108     * @deprecated since 10.1 - seems unused
109     */
110    @Deprecated
111    public static void copyTree(File src, File dst, PathFilter filter) throws IOException {
112        copyTree(src, dst, new Path("/"), filter);
113    }
114
115    public static void copyTree(File src, File dst, Path prefix, PathFilter filter) throws IOException {
116        if (!prefix.isAbsolute()) {
117            prefix = prefix.makeAbsolute();
118        }
119        int rootIndex = src.getPath().length() + 1;
120        for (File file : src.listFiles()) {
121            copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter);
122        }
123    }
124
125    protected static void copyTree(int rootIndex, File src, File dst, Path prefix, PathFilter filter)
126            throws IOException {
127        if (src.isFile()) {
128            String relPath = src.getPath().substring(rootIndex);
129            if (!filter.accept(new Path(relPath))) {
130                return;
131            }
132            if (!prefix.isRoot()) { // remove prefix from path
133                String path = dst.getPath();
134                String pff = prefix.toString();
135                int prefixIndex = path.lastIndexOf(pff);
136                if (prefixIndex > 0) {
137                    path = path.substring(0, prefixIndex) + path.substring(prefixIndex + pff.length());
138                    dst = new File(path);
139                }
140            }
141            dst.getParentFile().mkdirs();
142            copyFile(src, dst);
143        } else if (src.isDirectory()) {
144            File[] files = src.listFiles();
145            for (File file : files) {
146                copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter);
147            }
148        }
149    }
150
151    /**
152     * Decodes an URL path so that is can be processed as a filename later.
153     *
154     * @param url the Url to be processed.
155     * @return the decoded path.
156     */
157    public static String getFilePathFromUrl(URL url) {
158        String path = "";
159        if (url.getProtocol().equals("file")) {
160            try {
161                path = URLDecoder.decode(url.getPath(), "UTF-8");
162            } catch (UnsupportedEncodingException e) {
163                log.error(e);
164            }
165        }
166        return path;
167    }
168
169    public static File getFileFromURL(URL url) {
170        File file;
171        String filename = getFilePathFromUrl(url);
172        if (filename.equals("")) {
173            file = null;
174        } else {
175            file = new File(filename);
176        }
177        return file;
178    }
179
180    public static String getParentPath(String path) {
181        int p = path.lastIndexOf(File.separator);
182        if (p == -1) {
183            return null;
184        }
185        return path.substring(0, p);
186    }
187
188    public static String getFileName(String path) {
189        int p = path.lastIndexOf(File.separator);
190        if (p == -1) {
191            return path;
192        }
193        return path.substring(p + 1);
194    }
195
196    public static String getFileExtension(String path) {
197        int p = path.lastIndexOf('.');
198        if (p == -1) {
199            return null;
200        }
201        return path.substring(p + 1);
202    }
203
204    public static String getFileNameNoExt(String path) {
205        String name = getFileName(path);
206        int p = name.lastIndexOf('.');
207        if (p == -1) {
208            return name;
209        }
210        return name.substring(0, p);
211    }
212
213    /**
214     * Retrieves the total path of a resource from the Thread Context.
215     *
216     * @param resource the resource name to be retrieved.
217     * @return the decoded path.
218     */
219    public static String getResourcePathFromContext(String resource) {
220        URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
221        return getFilePathFromUrl(url);
222    }
223
224    public static File getResourceFileFromContext(String resource) {
225        File file;
226        String filename = getResourcePathFromContext(resource);
227        if (filename.equals("")) {
228            file = null;
229        } else {
230            file = new File(filename);
231        }
232        return file;
233    }
234
235    public static File[] findFiles(File root, String pattern, boolean recurse) {
236        List<File> result = new ArrayList<>();
237        if (pattern == null) {
238            if (recurse) {
239                collectFiles(root, result);
240            } else {
241                return root.listFiles();
242            }
243        } else {
244            FileNamePattern pat = new FileNamePattern(pattern);
245            if (recurse) {
246                collectFiles(root, pat, result);
247            } else {
248                File[] files = root.listFiles();
249                for (File file : files) {
250                    if (pat.match(file.getName())) {
251                        result.add(file);
252                    }
253                }
254            }
255        }
256        return result.toArray(new File[result.size()]);
257    }
258
259    public static void collectFiles(File root, FileNamePattern pattern, List<File> result) {
260        File[] files = root.listFiles();
261        for (File file : files) {
262            if (pattern.match(file.getName())) {
263                result.add(file);
264                if (file.isDirectory()) {
265                    collectFiles(file, pattern, result);
266                }
267            }
268        }
269    }
270
271    public static void collectFiles(File root, List<File> result) {
272        File[] files = root.listFiles();
273        for (File file : files) {
274            result.add(file);
275            if (file.isDirectory()) {
276                collectFiles(file, result);
277            }
278        }
279    }
280
281    /**
282     * Create a file handler (this doesn't create a real file) given a file URI. This method can be used to create files
283     * from invalid URL strings (e.g. containing spaces ..)
284     *
285     * @return a file object
286     */
287    public static File urlToFile(String url) throws MalformedURLException {
288        return urlToFile(new URL(url));
289    }
290
291    public static File urlToFile(URL url) {
292        try {
293            return new File(url.toURI());
294        } catch (URISyntaxException e) {
295            return new File(url.getPath());
296        }
297    }
298
299    /**
300     * Compares two files content as String even if their EOL are different
301     *
302     * @param expected a file content with Windows or Unix like EOL
303     * @param source another file content with Windows or Unix like EOL
304     * @return the result of equals after replacing their EOL
305     */
306    public static boolean areFilesContentEquals(String expected, String source) {
307        if (expected == source) {
308            return true;
309        }
310
311        if (expected == null || source == null) {
312            return false;
313        }
314
315        if (expected.length() != source.length()) {
316            // Prevent from comparing files with Windows EOL
317            return expected.replace("\r\n", "\n").equals(source.replace("\r\n", "\n"));
318        } else {
319            return expected.equals(source);
320        }
321    }
322
323    /**
324     * Returns a safe filename, replacing unsafe characters (: \ / * ..) with "_". For instance, it turns
325     * "tmp/../2349:876398/foo.png" into "tmp___2349_876398_foo.png"
326     *
327     * @param filename the filename
328     * @return the safe filename with underscores instead of unsafe characters
329     * @since 9.1
330     */
331    public static String getSafeFilename(String filename) {
332        return filename.replaceAll("(\\\\)|(\\/)|(\\:)|(\\*)|(\\.\\.)", "_");
333    }
334
335}