OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 149 | Rev 174 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 149 Rev 156
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.utils;
14
 package org.openconcerto.utils;
15
 
15
 
16
import org.openconcerto.utils.CollectionMap2.Mode;
16
import org.openconcerto.utils.CollectionMap2.Mode;
17
import org.openconcerto.utils.OSFamily.Unix;
17
import org.openconcerto.utils.OSFamily.Unix;
18
import org.openconcerto.utils.ProcessStreams.Action;
18
import org.openconcerto.utils.ProcessStreams.Action;
19
import org.openconcerto.utils.StringUtils.Escaper;
19
import org.openconcerto.utils.StringUtils.Escaper;
20
import org.openconcerto.utils.cc.ExnTransformer;
20
import org.openconcerto.utils.cc.ExnTransformer;
21
import org.openconcerto.utils.cc.IClosure;
21
import org.openconcerto.utils.cc.IClosure;
22
 
22
 
23
import java.awt.Desktop;
23
import java.awt.Desktop;
24
import java.io.BufferedReader;
24
import java.io.BufferedReader;
25
import java.io.BufferedWriter;
25
import java.io.BufferedWriter;
26
import java.io.File;
26
import java.io.File;
27
import java.io.FileFilter;
27
import java.io.FileFilter;
28
import java.io.FileInputStream;
28
import java.io.FileInputStream;
29
import java.io.FileNotFoundException;
29
import java.io.FileNotFoundException;
30
import java.io.FileOutputStream;
30
import java.io.FileOutputStream;
31
import java.io.IOException;
31
import java.io.IOException;
32
import java.io.InputStream;
32
import java.io.InputStream;
33
import java.io.InputStreamReader;
33
import java.io.InputStreamReader;
34
import java.io.OutputStreamWriter;
34
import java.io.OutputStreamWriter;
35
import java.io.RandomAccessFile;
35
import java.io.RandomAccessFile;
36
import java.io.Reader;
36
import java.io.Reader;
37
import java.net.URI;
37
import java.net.URI;
38
import java.net.URL;
38
import java.net.URL;
39
import java.nio.channels.FileChannel;
39
import java.nio.channels.FileChannel;
40
import java.nio.charset.Charset;
40
import java.nio.charset.Charset;
-
 
41
import java.nio.charset.StandardCharsets;
41
import java.nio.file.CopyOption;
42
import java.nio.file.CopyOption;
42
import java.nio.file.DirectoryStream;
43
import java.nio.file.DirectoryStream;
43
import java.nio.file.DirectoryStream.Filter;
44
import java.nio.file.DirectoryStream.Filter;
44
import java.nio.file.FileVisitResult;
45
import java.nio.file.FileVisitResult;
45
import java.nio.file.Files;
46
import java.nio.file.Files;
46
import java.nio.file.LinkOption;
47
import java.nio.file.LinkOption;
47
import java.nio.file.Path;
48
import java.nio.file.Path;
48
import java.nio.file.SimpleFileVisitor;
49
import java.nio.file.SimpleFileVisitor;
49
import java.nio.file.StandardCopyOption;
50
import java.nio.file.StandardCopyOption;
50
import java.nio.file.attribute.BasicFileAttributes;
51
import java.nio.file.attribute.BasicFileAttributes;
51
import java.nio.file.attribute.PosixFileAttributeView;
52
import java.nio.file.attribute.PosixFileAttributeView;
52
import java.nio.file.attribute.PosixFilePermissions;
53
import java.nio.file.attribute.PosixFilePermissions;
53
import java.util.ArrayList;
54
import java.util.ArrayList;
54
import java.util.Arrays;
55
import java.util.Arrays;
55
import java.util.Collection;
56
import java.util.Collection;
56
import java.util.Collections;
57
import java.util.Collections;
57
import java.util.EnumSet;
58
import java.util.EnumSet;
58
import java.util.HashMap;
59
import java.util.HashMap;
59
import java.util.HashSet;
60
import java.util.HashSet;
60
import java.util.List;
61
import java.util.List;
61
import java.util.Map;
62
import java.util.Map;
62
import java.util.Map.Entry;
63
import java.util.Map.Entry;
63
import java.util.Set;
64
import java.util.Set;
64
import java.util.logging.Level;
65
import java.util.logging.Level;
65
import java.util.regex.Pattern;
66
import java.util.regex.Pattern;
66
 
67
 
67
public final class FileUtils {
68
public final class FileUtils {
68
 
69
 
69
    private FileUtils() {
70
    private FileUtils() {
70
        // all static
71
        // all static
71
    }
72
    }
72
 
73
 
73
    public static void browseFile(final File f) throws IOException {
74
    public static void browseFile(final File f) throws IOException {
74
 
75
 
75
        if (Desktop.isDesktopSupported()) {
76
        if (Desktop.isDesktopSupported()) {
76
            Desktop d = Desktop.getDesktop();
77
            Desktop d = Desktop.getDesktop();
77
            if (d.isSupported(Desktop.Action.BROWSE)) {
78
            if (d.isSupported(Desktop.Action.BROWSE)) {
78
                d.browse(f.getCanonicalFile().toURI());
79
                d.browse(f.getCanonicalFile().toURI());
79
            } else {
80
            } else {
80
                openNative(f);
81
                openNative(f);
81
            }
82
            }
82
        } else {
83
        } else {
83
            openNative(f);
84
            openNative(f);
84
        }
85
        }
85
    }
86
    }
86
 
87
 
87
    public static void browse(URI uri) throws Exception {
88
    public static void browse(URI uri) throws Exception {
88
        final boolean windows = System.getProperty("os.name").startsWith("Windows");
89
        final boolean windows = System.getProperty("os.name").startsWith("Windows");
89
        if (windows) {
90
        if (windows) {
90
            Desktop.getDesktop().browse(uri);
91
            Desktop.getDesktop().browse(uri);
91
        } else {
92
        } else {
92
            String[] cmdarray = new String[] { "xdg-open", uri.toString() };
93
            String[] cmdarray = new String[] { "xdg-open", uri.toString() };
93
            final int res = Runtime.getRuntime().exec(cmdarray).waitFor();
94
            final int res = Runtime.getRuntime().exec(cmdarray).waitFor();
94
            if (res != 0)
95
            if (res != 0)
95
                throw new IOException("error (" + res + ") executing " + Arrays.asList(cmdarray));
96
                throw new IOException("error (" + res + ") executing " + Arrays.asList(cmdarray));
96
        }
97
        }
97
    }
98
    }
98
 
99
 
99
    public static void openFile(File f) throws IOException {
100
    public static void openFile(File f) throws IOException {
-
 
101
        if (!f.exists()) {
-
 
102
            throw new FileNotFoundException(f.getAbsolutePath() + " not found");
-
 
103
        }
100
        if (Desktop.isDesktopSupported()) {
104
        if (Desktop.isDesktopSupported()) {
101
            Desktop d = Desktop.getDesktop();
105
            Desktop d = Desktop.getDesktop();
102
            if (d.isSupported(Desktop.Action.OPEN)) {
106
            if (d.isSupported(Desktop.Action.OPEN)) {
103
                d.open(f.getCanonicalFile());
107
                d.open(f.getCanonicalFile());
104
            } else {
108
            } else {
105
                openNative(f);
109
                openNative(f);
106
            }
110
            }
107
        } else {
111
        } else {
108
            openNative(f);
112
            openNative(f);
109
        }
113
        }
110
    }
114
    }
111
 
115
 
112
    // immutable, getAbsoluteFile() required otherwise list() returns null
116
    // immutable, getAbsoluteFile() required otherwise list() returns null
113
    static private final File WD = new File("").getAbsoluteFile();
117
    static private final File WD = new File("").getAbsoluteFile();
114
 
118
 
115
    static public final File getWD() {
119
    static public final File getWD() {
116
        return WD;
120
        return WD;
117
    }
121
    }
118
 
122
 
119
    /**
123
    /**
120
     * All the files (see {@link File#isFile()}) contained in the passed dir.
124
     * All the files (see {@link File#isFile()}) contained in the passed dir.
121
     * 
125
     * 
122
     * @param dir the root directory to search.
126
     * @param dir the root directory to search.
123
     * @return a List of String.
127
     * @return a List of String.
124
     */
128
     */
125
    public static List<String> listR(File dir) {
129
    public static List<String> listR(File dir) {
126
        return listR(dir, REGULAR_FILE_FILTER);
130
        return listR(dir, REGULAR_FILE_FILTER);
127
    }
131
    }
128
 
132
 
129
    public static List<String> listR(File dir, FileFilter ff) {
133
    public static List<String> listR(File dir, FileFilter ff) {
130
        return listR_rec(dir, ff, ".");
134
        return listR_rec(dir, ff, ".");
131
    }
135
    }
132
 
136
 
133
    private static List<String> listR_rec(File dir, FileFilter ff, String prefix) {
137
    private static List<String> listR_rec(File dir, FileFilter ff, String prefix) {
134
        if (!dir.isDirectory())
138
        if (!dir.isDirectory())
135
            return null;
139
            return null;
136
 
140
 
137
        final List<String> res = new ArrayList<String>();
141
        final List<String> res = new ArrayList<String>();
138
        final File[] children = dir.listFiles();
142
        final File[] children = dir.listFiles();
139
        for (int i = 0; i < children.length; i++) {
143
        for (int i = 0; i < children.length; i++) {
140
            final String newPrefix = prefix + "/" + children[i].getName();
144
            final String newPrefix = prefix + "/" + children[i].getName();
141
            if (ff == null || ff.accept(children[i])) {
145
            if (ff == null || ff.accept(children[i])) {
142
                res.add(newPrefix);
146
                res.add(newPrefix);
143
            }
147
            }
144
            if (children[i].isDirectory()) {
148
            if (children[i].isDirectory()) {
145
                res.addAll(listR_rec(children[i], ff, newPrefix));
149
                res.addAll(listR_rec(children[i], ff, newPrefix));
146
            }
150
            }
147
        }
151
        }
148
        return res;
152
        return res;
149
    }
153
    }
150
 
154
 
151
    public static void walk(File dir, IClosure<File> c) {
155
    public static void walk(File dir, IClosure<File> c) {
152
        walk(dir, c, RecursionType.BREADTH_FIRST);
156
        walk(dir, c, RecursionType.BREADTH_FIRST);
153
    }
157
    }
154
 
158
 
155
    public static void walk(File dir, IClosure<File> c, RecursionType type) {
159
    public static void walk(File dir, IClosure<File> c, RecursionType type) {
156
        if (type == RecursionType.BREADTH_FIRST)
160
        if (type == RecursionType.BREADTH_FIRST)
157
            c.executeChecked(dir);
161
            c.executeChecked(dir);
158
        if (dir.isDirectory()) {
162
        if (dir.isDirectory()) {
159
            for (final File child : dir.listFiles()) {
163
            for (final File child : dir.listFiles()) {
160
                walk(child, c, type);
164
                walk(child, c, type);
161
            }
165
            }
162
        }
166
        }
163
        if (type == RecursionType.DEPTH_FIRST)
167
        if (type == RecursionType.DEPTH_FIRST)
164
            c.executeChecked(dir);
168
            c.executeChecked(dir);
165
    }
169
    }
166
 
170
 
167
    public static final List<File> list(File root, final int depth) {
171
    public static final List<File> list(File root, final int depth) {
168
        return list(root, depth, null);
172
        return list(root, depth, null);
169
    }
173
    }
170
 
174
 
171
    /**
175
    /**
172
     * Finds all files at the specified depth below <code>root</code>.
176
     * Finds all files at the specified depth below <code>root</code>.
173
     * 
177
     * 
174
     * @param root the base directory
178
     * @param root the base directory
175
     * @param depth the depth of the returned files.
179
     * @param depth the depth of the returned files.
176
     * @param ff a filter, can be <code>null</code>.
180
     * @param ff a filter, can be <code>null</code>.
177
     * @return a list of files <code>depth</code> levels beneath <code>root</code>.
181
     * @return a list of files <code>depth</code> levels beneath <code>root</code>.
178
     */
182
     */
179
    public static final List<File> list(File root, final int depth, final FileFilter ff) {
183
    public static final List<File> list(File root, final int depth, final FileFilter ff) {
180
        return list(root, depth, depth, ff);
184
        return list(root, depth, depth, ff);
181
    }
185
    }
182
 
186
 
183
    public static final List<File> list(final File root, final int minDepth, final int maxDepth, final FileFilter ff) {
187
    public static final List<File> list(final File root, final int minDepth, final int maxDepth, final FileFilter ff) {
184
        return list(root, minDepth, maxDepth, ff, false);
188
        return list(root, minDepth, maxDepth, ff, false);
185
    }
189
    }
186
 
190
 
187
    public static final List<File> list(final File root, final int minDepth, final int maxDepth, final FileFilter ff, final boolean sort) {
191
    public static final List<File> list(final File root, final int minDepth, final int maxDepth, final FileFilter ff, final boolean sort) {
188
        if (minDepth > maxDepth)
192
        if (minDepth > maxDepth)
189
            throw new IllegalArgumentException(minDepth + " > " + maxDepth);
193
            throw new IllegalArgumentException(minDepth + " > " + maxDepth);
190
        if (maxDepth < 0)
194
        if (maxDepth < 0)
191
            throw new IllegalArgumentException(maxDepth + " < 0");
195
            throw new IllegalArgumentException(maxDepth + " < 0");
192
        if (!root.exists())
196
        if (!root.exists())
193
            return Collections.<File> emptyList();
197
            return Collections.<File> emptyList();
194
 
198
 
195
        final File currentFile = accept(ff, minDepth, maxDepth, root, 0) ? root : null;
199
        final File currentFile = accept(ff, minDepth, maxDepth, root, 0) ? root : null;
196
        if (maxDepth == 0) {
200
        if (maxDepth == 0) {
197
            return currentFile == null ? Collections.<File> emptyList() : Collections.singletonList(currentFile);
201
            return currentFile == null ? Collections.<File> emptyList() : Collections.singletonList(currentFile);
198
        } else {
202
        } else {
199
            final List<File> res = new ArrayList<File>();
203
            final List<File> res = new ArrayList<File>();
200
            final File[] children = root.listFiles();
204
            final File[] children = root.listFiles();
201
            if (children == null)
205
            if (children == null)
202
                throw new IllegalStateException("cannot list " + root);
206
                throw new IllegalStateException("cannot list " + root);
203
            if (sort)
207
            if (sort)
204
                Arrays.sort(children);
208
                Arrays.sort(children);
205
            for (final File child : children) {
209
            for (final File child : children) {
206
                if (maxDepth > 1 && child.isDirectory()) {
210
                if (maxDepth > 1 && child.isDirectory()) {
207
                    res.addAll(list(child, minDepth - 1, maxDepth - 1, ff, sort));
211
                    res.addAll(list(child, minDepth - 1, maxDepth - 1, ff, sort));
208
                } else if (accept(ff, minDepth, maxDepth, child, 1)) {
212
                } else if (accept(ff, minDepth, maxDepth, child, 1)) {
209
                    res.add(child);
213
                    res.add(child);
210
                }
214
                }
211
            }
215
            }
212
            if (currentFile != null)
216
            if (currentFile != null)
213
                res.add(currentFile);
217
                res.add(currentFile);
214
            return res;
218
            return res;
215
        }
219
        }
216
    }
220
    }
217
 
221
 
218
    private static final boolean accept(final FileFilter ff, final int minDepth, final int maxDepth, final File f, final int depth) {
222
    private static final boolean accept(final FileFilter ff, final int minDepth, final int maxDepth, final File f, final int depth) {
219
        return minDepth <= depth && depth <= maxDepth && (ff == null || ff.accept(f));
223
        return minDepth <= depth && depth <= maxDepth && (ff == null || ff.accept(f));
220
    }
224
    }
221
 
225
 
222
    /**
226
    /**
223
     * Returns the relative path from one file to another in the same filesystem tree. Files are not
227
     * Returns the relative path from one file to another in the same filesystem tree. Files are not
224
     * required to exist, see {@link File#getCanonicalPath()}.
228
     * required to exist, see {@link File#getCanonicalPath()}.
225
     * 
229
     * 
226
     * @param fromDir the starting directory, eg /a/b/.
230
     * @param fromDir the starting directory, eg /a/b/.
227
     * @param to the file to get to, eg /a/x/y.txt.
231
     * @param to the file to get to, eg /a/x/y.txt.
228
     * @return the relative path, eg "../x/y.txt".
232
     * @return the relative path, eg "../x/y.txt".
229
     * @throws IOException if an error occurs while canonicalizing the files.
233
     * @throws IOException if an error occurs while canonicalizing the files.
230
     * @throws IllegalArgumentException if fromDir exists and is not directory.
234
     * @throws IllegalArgumentException if fromDir exists and is not directory.
231
     */
235
     */
232
    public static final String relative(File fromDir, File to) throws IOException {
236
    public static final String relative(File fromDir, File to) throws IOException {
233
        if (fromDir.exists() && !fromDir.isDirectory())
237
        if (fromDir.exists() && !fromDir.isDirectory())
234
            throw new IllegalArgumentException(fromDir + " is not a directory");
238
            throw new IllegalArgumentException(fromDir + " is not a directory");
235
 
239
 
236
        final File fromF = fromDir.getCanonicalFile();
240
        final File fromF = fromDir.getCanonicalFile();
237
        final File toF = to.getCanonicalFile();
241
        final File toF = to.getCanonicalFile();
238
        final List<File> toPath = getAncestors(toF);
242
        final List<File> toPath = getAncestors(toF);
239
        final List<File> fromPath = getAncestors(fromF);
243
        final List<File> fromPath = getAncestors(fromF);
240
 
244
 
241
        // no common ancestor (for example on Windows on 2 different letters)
245
        // no common ancestor (for example on Windows on 2 different letters)
242
        if (!toPath.get(0).equals(fromPath.get(0))) {
246
        if (!toPath.get(0).equals(fromPath.get(0))) {
243
            // already canonical
247
            // already canonical
244
            return toF.getPath();
248
            return toF.getPath();
245
        }
249
        }
246
 
250
 
247
        int commonIndex = Math.min(toPath.size(), fromPath.size()) - 1;
251
        int commonIndex = Math.min(toPath.size(), fromPath.size()) - 1;
248
        boolean found = false;
252
        boolean found = false;
249
        while (commonIndex >= 0 && !found) {
253
        while (commonIndex >= 0 && !found) {
250
            found = fromPath.get(commonIndex).equals(toPath.get(commonIndex));
254
            found = fromPath.get(commonIndex).equals(toPath.get(commonIndex));
251
            if (!found)
255
            if (!found)
252
                commonIndex--;
256
                commonIndex--;
253
        }
257
        }
254
 
258
 
255
        // on remonte jusqu'à l'ancêtre commun
259
        // on remonte jusqu'à l'ancêtre commun
256
        final List<String> complete = new ArrayList<String>(Collections.nCopies(fromPath.size() - 1 - commonIndex, ".."));
260
        final List<String> complete = new ArrayList<String>(Collections.nCopies(fromPath.size() - 1 - commonIndex, ".."));
257
        if (complete.isEmpty())
261
        if (complete.isEmpty())
258
            complete.add(".");
262
            complete.add(".");
259
        // puis on descend vers 'to'
263
        // puis on descend vers 'to'
260
        for (File f : toPath.subList(commonIndex + 1, toPath.size())) {
264
        for (File f : toPath.subList(commonIndex + 1, toPath.size())) {
261
            complete.add(f.getName());
265
            complete.add(f.getName());
262
        }
266
        }
263
 
267
 
264
        return CollectionUtils.join(complete, File.separator);
268
        return CollectionUtils.join(complete, File.separator);
265
    }
269
    }
266
 
270
 
267
    // return each ancestor of f (including itself)
271
    // return each ancestor of f (including itself)
268
    // eg [/, /folder, /folder/dir] for /folder/dir
272
    // eg [/, /folder, /folder/dir] for /folder/dir
269
    public final static List<File> getAncestors(File f) {
273
    public final static List<File> getAncestors(File f) {
270
        final List<File> path = new ArrayList<File>();
274
        final List<File> path = new ArrayList<File>();
271
        File currentF = f;
275
        File currentF = f;
272
        while (currentF != null) {
276
        while (currentF != null) {
273
            path.add(0, currentF);
277
            path.add(0, currentF);
274
            currentF = currentF.getParentFile();
278
            currentF = currentF.getParentFile();
275
        }
279
        }
276
        return path;
280
        return path;
277
    }
281
    }
278
 
282
 
279
    public final static File addSuffix(File f, String suffix) {
283
    public final static File addSuffix(File f, String suffix) {
280
        return new File(f.getParentFile(), f.getName() + suffix);
284
        return new File(f.getParentFile(), f.getName() + suffix);
281
    }
285
    }
282
 
286
 
283
    /**
287
    /**
284
     * Prepend a string to a suffix.
288
     * Prepend a string to a suffix.
285
     * 
289
     * 
286
     * @param f the file, e.g. "sample.xml".
290
     * @param f the file, e.g. "sample.xml".
287
     * @param toInsert the string to insert in the filename, e.g. "-sql".
291
     * @param toInsert the string to insert in the filename, e.g. "-sql".
288
     * @param suffix the suffix of <code>f</code>, e.g. ".xml".
292
     * @param suffix the suffix of <code>f</code>, e.g. ".xml".
289
     * @return a new file with <code>toInsert</code> prepended to <code>suffix</code>, e.g.
293
     * @return a new file with <code>toInsert</code> prepended to <code>suffix</code>, e.g.
290
     *         "sample-sql.xml".
294
     *         "sample-sql.xml".
291
     */
295
     */
292
    public final static File prependSuffix(File f, String toInsert, String suffix) {
296
    public final static File prependSuffix(File f, String toInsert, String suffix) {
293
        return new File(f.getParentFile(), removeSuffix(f.getName(), suffix) + toInsert + suffix);
297
        return new File(f.getParentFile(), removeSuffix(f.getName(), suffix) + toInsert + suffix);
294
    }
298
    }
295
 
299
 
296
    /**
300
    /**
297
     * Prepend a string to a suffix.
301
     * Prepend a string to a suffix.
298
     * 
302
     * 
299
     * @param f the file, e.g. "sample.xml".
303
     * @param f the file, e.g. "sample.xml".
300
     * @param toInsert the string to insert in the filename, e.g. "-sql".
304
     * @param toInsert the string to insert in the filename, e.g. "-sql".
301
     * @param suffix the suffix of <code>f</code>, e.g. ".xml".
305
     * @param suffix the suffix of <code>f</code>, e.g. ".xml".
302
     * @return a new file with <code>toInsert</code> prepended to <code>suffix</code>, e.g.
306
     * @return a new file with <code>toInsert</code> prepended to <code>suffix</code>, e.g.
303
     *         "sample-sql.xml".
307
     *         "sample-sql.xml".
304
     */
308
     */
305
    public final static Path prependSuffix(Path f, String toInsert, String suffix) {
309
    public final static Path prependSuffix(Path f, String toInsert, String suffix) {
306
        return f.resolveSibling(removeSuffix(f.getFileName().toString(), suffix) + toInsert + suffix);
310
        return f.resolveSibling(removeSuffix(f.getFileName().toString(), suffix) + toInsert + suffix);
307
    }
311
    }
308
 
312
 
309
    public final static String removeSuffix(String name, String suffix) {
313
    public final static String removeSuffix(String name, String suffix) {
310
        return name.endsWith(suffix) ? name.substring(0, name.length() - suffix.length()) : name;
314
        return name.endsWith(suffix) ? name.substring(0, name.length() - suffix.length()) : name;
311
    }
315
    }
312
 
316
 
313
    /**
317
    /**
314
     * Rename a file if necessary by finding a free name. The tested names are
318
     * Rename a file if necessary by finding a free name. The tested names are
315
     * <code>name + "_" + i + suffix</code>.
319
     * <code>name + "_" + i + suffix</code>.
316
     * 
320
     * 
317
     * @param parent the directory.
321
     * @param parent the directory.
318
     * @param name the base name of the file.
322
     * @param name the base name of the file.
319
     * @param suffix the suffix of the file, e.g. ".ods".
323
     * @param suffix the suffix of the file, e.g. ".ods".
320
     * @return <code>new File(parent, name + suffix)</code> (always non existing) and the new file,
324
     * @return <code>new File(parent, name + suffix)</code> (always non existing) and the new file,
321
     *         (or <code>null</code> if no file was moved).
325
     *         (or <code>null</code> if no file was moved).
322
     */
326
     */
323
    public final static File[] mvOut(final File parent, final String name, final String suffix) {
327
    public final static File[] mvOut(final File parent, final String name, final String suffix) {
324
        final File fDest = new File(parent, name + suffix);
328
        final File fDest = new File(parent, name + suffix);
325
        final File renamed;
329
        final File renamed;
326
        if (fDest.exists()) {
330
        if (fDest.exists()) {
327
            int i = 0;
331
            int i = 0;
328
            File free = fDest;
332
            File free = fDest;
329
            while (free.exists()) {
333
            while (free.exists()) {
330
                free = new File(parent, name + "_" + i + suffix);
334
                free = new File(parent, name + "_" + i + suffix);
331
                i++;
335
                i++;
332
            }
336
            }
333
            assert !fDest.equals(free);
337
            assert !fDest.equals(free);
334
            if (!fDest.renameTo(free))
338
            if (!fDest.renameTo(free))
335
                throw new IllegalStateException("Couldn't rename " + fDest + " to " + free);
339
                throw new IllegalStateException("Couldn't rename " + fDest + " to " + free);
336
            renamed = free;
340
            renamed = free;
337
        } else {
341
        } else {
338
            renamed = null;
342
            renamed = null;
339
        }
343
        }
340
        assert !fDest.exists();
344
        assert !fDest.exists();
341
        return new File[] { fDest, renamed };
345
        return new File[] { fDest, renamed };
342
    }
346
    }
343
 
347
 
344
    // ** shell
348
    // ** shell
345
 
349
 
346
    /**
350
    /**
347
     * Behave like the 'mv' unix utility, ie handle cross filesystems mv and <code>dest</code> being
351
     * Behave like the 'mv' unix utility, ie handle cross filesystems mv and <code>dest</code> being
348
     * a directory.
352
     * a directory.
349
     * 
353
     * 
350
     * @param f the source file.
354
     * @param f the source file.
351
     * @param dest the destination file or directory.
355
     * @param dest the destination file or directory.
352
     * @return the error or <code>null</code> if there was none.
356
     * @return the error or <code>null</code> if there was none.
353
     */
357
     */
354
    public static String mv(File f, File dest) {
358
    public static String mv(File f, File dest) {
355
        final File canonF;
359
        final File canonF;
356
        File canonDest;
360
        File canonDest;
357
        try {
361
        try {
358
            canonF = f.getCanonicalFile();
362
            canonF = f.getCanonicalFile();
359
            canonDest = dest.getCanonicalFile();
363
            canonDest = dest.getCanonicalFile();
360
        } catch (IOException e) {
364
        } catch (IOException e) {
361
            return ExceptionUtils.getStackTrace(e);
365
            return ExceptionUtils.getStackTrace(e);
362
        }
366
        }
363
        if (canonF.equals(canonDest))
367
        if (canonF.equals(canonDest))
364
            // nothing to do
368
            // nothing to do
365
            return null;
369
            return null;
366
        if (canonDest.isDirectory())
370
        if (canonDest.isDirectory())
367
            canonDest = new File(canonDest, canonF.getName());
371
            canonDest = new File(canonDest, canonF.getName());
368
 
372
 
369
        final File destF;
373
        final File destF;
370
        if (canonDest.exists())
374
        if (canonDest.exists())
371
            return canonDest + " exists";
375
            return canonDest + " exists";
372
        else if (!canonDest.getParentFile().exists())
376
        else if (!canonDest.getParentFile().exists())
373
            return "parent of " + canonDest + " does not exist";
377
            return "parent of " + canonDest + " does not exist";
374
        else
378
        else
375
            destF = canonDest;
379
            destF = canonDest;
376
        if (!canonF.renameTo(destF)) {
380
        if (!canonF.renameTo(destF)) {
377
            try {
381
            try {
378
                copyDirectory(canonF, destF);
382
                copyDirectory(canonF, destF);
379
                if (destF.exists())
383
                if (destF.exists())
380
                    rm_R(canonF);
384
                    rm_R(canonF);
381
            } catch (IOException e) {
385
            } catch (IOException e) {
382
                return ExceptionUtils.getStackTrace(e);
386
                return ExceptionUtils.getStackTrace(e);
383
            }
387
            }
384
        }
388
        }
385
        return null;
389
        return null;
386
    }
390
    }
387
 
391
 
388
    // transferTo() can be limited by a number of factors, like the number of bits of the system
392
    // transferTo() can be limited by a number of factors, like the number of bits of the system
389
    // if mmap is used (e.g. on Linux) or by an arbitrary magic number on Windows : 64Mb - 32Kb
393
    // if mmap is used (e.g. on Linux) or by an arbitrary magic number on Windows : 64Mb - 32Kb
390
    private static final int CHANNEL_MAX_COUNT = Math.min(64 * 1024 * 1024 - 32 * 1024, Integer.MAX_VALUE);
394
    private static final int CHANNEL_MAX_COUNT = Math.min(64 * 1024 * 1024 - 32 * 1024, Integer.MAX_VALUE);
391
 
395
 
392
    public static void copyFile(File in, File out) throws IOException {
396
    public static void copyFile(File in, File out) throws IOException {
393
        copyFile(in, out, CHANNEL_MAX_COUNT);
397
        copyFile(in, out, CHANNEL_MAX_COUNT);
394
    }
398
    }
395
 
399
 
396
    /**
400
    /**
397
     * Copy a file. It is generally not advised to use 0 for <code>maxCount</code> since various
401
     * Copy a file. It is generally not advised to use 0 for <code>maxCount</code> since various
398
     * implementations have size limitations, see {@link #copyFile(File, File)}.
402
     * implementations have size limitations, see {@link #copyFile(File, File)}.
399
     * 
403
     * 
400
     * @param in the source file.
404
     * @param in the source file.
401
     * @param out the destination file.
405
     * @param out the destination file.
402
     * @param maxCount the number of bytes to copy at a time, 0 meaning size of <code>in</code>.
406
     * @param maxCount the number of bytes to copy at a time, 0 meaning size of <code>in</code>.
403
     * @throws IOException if an error occurs.
407
     * @throws IOException if an error occurs.
404
     */
408
     */
405
    public static void copyFile(File in, File out, long maxCount) throws IOException {
409
    public static void copyFile(File in, File out, long maxCount) throws IOException {
406
        try (final FileInputStream sourceIn = new FileInputStream(in); final FileOutputStream sourceOut = new FileOutputStream(out);) {
410
        try (final FileInputStream sourceIn = new FileInputStream(in); final FileOutputStream sourceOut = new FileOutputStream(out);) {
407
            final FileChannel sourceChannel = sourceIn.getChannel();
411
            final FileChannel sourceChannel = sourceIn.getChannel();
408
            final long size = sourceChannel.size();
412
            final long size = sourceChannel.size();
409
            if (maxCount == 0)
413
            if (maxCount == 0)
410
                maxCount = size;
414
                maxCount = size;
411
            final FileChannel destinationChannel = sourceOut.getChannel();
415
            final FileChannel destinationChannel = sourceOut.getChannel();
412
            long position = 0;
416
            long position = 0;
413
            while (position < size) {
417
            while (position < size) {
414
                position += sourceChannel.transferTo(position, maxCount, destinationChannel);
418
                position += sourceChannel.transferTo(position, maxCount, destinationChannel);
415
            }
419
            }
416
        }
420
        }
417
    }
421
    }
418
 
422
 
419
    public static void copyFile(File in, File out, final boolean useTime) throws IOException {
423
    public static void copyFile(File in, File out, final boolean useTime) throws IOException {
420
        if (!useTime || in.lastModified() != out.lastModified()) {
424
        if (!useTime || in.lastModified() != out.lastModified()) {
421
            copyFile(in, out);
425
            copyFile(in, out);
422
            if (useTime)
426
            if (useTime)
423
                out.setLastModified(in.lastModified());
427
                out.setLastModified(in.lastModified());
424
        }
428
        }
425
    }
429
    }
426
 
430
 
427
    public static void copyDirectory(File in, File out) throws IOException {
431
    public static void copyDirectory(File in, File out) throws IOException {
428
        copyDirectory(in, out, Collections.<String> emptySet());
432
        copyDirectory(in, out, Collections.<String> emptySet());
429
    }
433
    }
430
 
434
 
431
    public static final Set<String> VersionControl = CollectionUtils.createSet(".svn", "CVS");
435
    public static final Set<String> VersionControl = CollectionUtils.createSet(".svn", "CVS");
432
 
436
 
433
    public static void copyDirectory(File in, File out, final Set<String> toIgnore) throws IOException {
437
    public static void copyDirectory(File in, File out, final Set<String> toIgnore) throws IOException {
434
        copyDirectory(in, out, toIgnore, false);
438
        copyDirectory(in, out, toIgnore, false);
435
    }
439
    }
436
 
440
 
437
    public static void copyDirectory(File in, File out, final Set<String> toIgnore, final boolean useTime) throws IOException {
441
    public static void copyDirectory(File in, File out, final Set<String> toIgnore, final boolean useTime) throws IOException {
438
        if (toIgnore.contains(in.getName()))
442
        if (toIgnore.contains(in.getName()))
439
            return;
443
            return;
440
 
444
 
441
        if (in.isDirectory()) {
445
        if (in.isDirectory()) {
442
            if (!out.exists()) {
446
            if (!out.exists()) {
443
                out.mkdir();
447
                out.mkdir();
444
            }
448
            }
445
 
449
 
446
            String[] children = in.list();
450
            String[] children = in.list();
447
            for (int i = 0; i < children.length; i++) {
451
            for (int i = 0; i < children.length; i++) {
448
                copyDirectory(new File(in, children[i]), new File(out, children[i]), toIgnore, useTime);
452
                copyDirectory(new File(in, children[i]), new File(out, children[i]), toIgnore, useTime);
449
            }
453
            }
450
        } else {
454
        } else {
451
            if (!in.getName().equals("Thumbs.db")) {
455
            if (!in.getName().equals("Thumbs.db")) {
452
                copyFile(in, out, useTime);
456
                copyFile(in, out, useTime);
453
            }
457
            }
454
        }
458
        }
455
    }
459
    }
456
 
460
 
457
    public static void copyDirectory(Path in, Path out) throws IOException {
461
    public static void copyDirectory(Path in, Path out) throws IOException {
458
        copyDirectory(in, out, false);
462
        copyDirectory(in, out, false);
459
    }
463
    }
460
 
464
 
461
    public static void copyDirectory(Path in, Path out, final boolean hardLink, final CopyOption... copyOptions) throws IOException {
465
    public static void copyDirectory(Path in, Path out, final boolean hardLink, final CopyOption... copyOptions) throws IOException {
462
        copyDirectory(in, out, Collections.<String> emptySet(), hardLink, false, Arrays.asList(copyOptions));
466
        copyDirectory(in, out, Collections.<String> emptySet(), hardLink, false, Arrays.asList(copyOptions));
463
    }
467
    }
464
 
468
 
465
    public static void copyDirectory(Path in, Path out, final Set<String> toIgnore, final boolean hardLink, final boolean followLinks, final List<CopyOption> copyOptions) throws IOException {
469
    public static void copyDirectory(Path in, Path out, final Set<String> toIgnore, final boolean hardLink, final boolean followLinks, final List<CopyOption> copyOptions) throws IOException {
466
        final LinkOption[] isDirOptions = followLinks ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
470
        final LinkOption[] isDirOptions = followLinks ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
467
        final Set<CopyOption> copyOptionsP = new HashSet<>(copyOptions);
471
        final Set<CopyOption> copyOptionsP = new HashSet<>(copyOptions);
468
        if (followLinks) {
472
        if (followLinks) {
469
            copyOptionsP.remove(LinkOption.NOFOLLOW_LINKS);
473
            copyOptionsP.remove(LinkOption.NOFOLLOW_LINKS);
470
        } else {
474
        } else {
471
            copyOptionsP.add(LinkOption.NOFOLLOW_LINKS);
475
            copyOptionsP.add(LinkOption.NOFOLLOW_LINKS);
472
        }
476
        }
473
        copyDirectory(in, out, toIgnore, isDirOptions, copyOptionsP.toArray(new CopyOption[copyOptionsP.size()]), hardLink);
477
        copyDirectory(in, out, toIgnore, isDirOptions, copyOptionsP.toArray(new CopyOption[copyOptionsP.size()]), hardLink);
474
    }
478
    }
475
 
479
 
476
    public static void copyDirectory(Path in, Path out, final Set<String> toIgnore, final LinkOption[] isDirOptions, final CopyOption[] copyOptions, final boolean hardLink) throws IOException {
480
    public static void copyDirectory(Path in, Path out, final Set<String> toIgnore, final LinkOption[] isDirOptions, final CopyOption[] copyOptions, final boolean hardLink) throws IOException {
477
        if (toIgnore.contains(in.getFileName().toString()))
481
        if (toIgnore.contains(in.getFileName().toString()))
478
            return;
482
            return;
479
 
483
 
480
        if (Files.isDirectory(in, isDirOptions)) {
484
        if (Files.isDirectory(in, isDirOptions)) {
481
            Files.copy(in, out, copyOptions);
485
            Files.copy(in, out, copyOptions);
482
 
486
 
483
            try (final DirectoryStream<Path> dirStream = Files.newDirectoryStream(in)) {
487
            try (final DirectoryStream<Path> dirStream = Files.newDirectoryStream(in)) {
484
                for (final Path child : dirStream) {
488
                for (final Path child : dirStream) {
485
                    copyDirectory(child, out.resolve(child.getFileName()), toIgnore, isDirOptions, copyOptions, hardLink);
489
                    copyDirectory(child, out.resolve(child.getFileName()), toIgnore, isDirOptions, copyOptions, hardLink);
486
                }
490
                }
487
            }
491
            }
488
            // fix up modification time of directory when done
492
            // fix up modification time of directory when done
489
            // (other attributes have already been copied at creation time)
493
            // (other attributes have already been copied at creation time)
490
            if (Arrays.asList(copyOptions).contains(StandardCopyOption.COPY_ATTRIBUTES))
494
            if (Arrays.asList(copyOptions).contains(StandardCopyOption.COPY_ATTRIBUTES))
491
                Files.setLastModifiedTime(out, Files.getLastModifiedTime(in));
495
                Files.setLastModifiedTime(out, Files.getLastModifiedTime(in));
492
        } else {
496
        } else {
493
            if (!in.getFileName().toString().equals("Thumbs.db")) {
497
            if (!in.getFileName().toString().equals("Thumbs.db")) {
494
                if (hardLink)
498
                if (hardLink)
495
                    Files.createLink(out, in);
499
                    Files.createLink(out, in);
496
                else
500
                else
497
                    Files.copy(in, out, copyOptions);
501
                    Files.copy(in, out, copyOptions);
498
            }
502
            }
499
        }
503
        }
500
    }
504
    }
501
 
505
 
502
    /**
506
    /**
503
     * Delete recursively the passed directory. If a deletion fails, the method stops attempting to
507
     * Delete recursively the passed directory. If a deletion fails, the method stops attempting to
504
     * delete and returns false.
508
     * delete and returns false.
505
     * 
509
     * 
506
     * @param dir the dir to be deleted.
510
     * @param dir the dir to be deleted.
507
     * @return <code>true</code> if all deletions were successful.
511
     * @return <code>true</code> if all deletions were successful.
508
     * @deprecated callers often forget to check return value, see also {@link #rm_R(Path)}
512
     * @deprecated callers often forget to check return value, see also {@link #rm_R(Path)}
509
     */
513
     */
510
    public static boolean rmR(File dir) {
514
    public static boolean rmR(File dir) {
511
        if (dir.isDirectory()) {
515
        if (dir.isDirectory()) {
512
            File[] children = dir.listFiles();
516
            File[] children = dir.listFiles();
513
            for (int i = 0; i < children.length; i++) {
517
            for (int i = 0; i < children.length; i++) {
514
                boolean success = rmR(children[i]);
518
                boolean success = rmR(children[i]);
515
 
519
 
516
                if (!success) {
520
                if (!success) {
517
 
521
 
518
                    return false;
522
                    return false;
519
                }
523
                }
520
            }
524
            }
521
        }
525
        }
522
 
526
 
523
        // The directory is now empty so delete it
527
        // The directory is now empty so delete it
524
        return dir.delete();
528
        return dir.delete();
525
    }
529
    }
526
 
530
 
527
    public static void rm_R(File dir) throws IOException {
531
    public static void rm_R(File dir) throws IOException {
528
        rm_R(dir.toPath());
532
        rm_R(dir.toPath());
529
    }
533
    }
530
 
534
 
531
    public static void rm_R(Path dir) throws IOException {
535
    public static void rm_R(Path dir) throws IOException {
532
        rm_R(dir, false);
536
        rm_R(dir, false);
533
    }
537
    }
534
 
538
 
535
    /**
539
    /**
536
     * Delete recursively the passed file.
540
     * Delete recursively the passed file.
537
     * 
541
     * 
538
     * @param dir the file to delete.
542
     * @param dir the file to delete.
539
     * @param mustExist what to do if <code>dir</code> is missing : <code>false</code> if it is not
543
     * @param mustExist what to do if <code>dir</code> is missing : <code>false</code> if it is not
540
     *        an error, <code>true</code> to fail.
544
     *        an error, <code>true</code> to fail.
541
     * @throws IOException if a deletion fails.
545
     * @throws IOException if a deletion fails.
542
     */
546
     */
543
    public static void rm_R(final Path dir, final boolean mustExist) throws IOException {
547
    public static void rm_R(final Path dir, final boolean mustExist) throws IOException {
544
        if (!mustExist && !Files.exists(dir))
548
        if (!mustExist && !Files.exists(dir))
545
            return;
549
            return;
546
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
550
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
547
            @Override
551
            @Override
548
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
552
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
549
                Files.delete(file);
553
                Files.delete(file);
550
                return FileVisitResult.CONTINUE;
554
                return FileVisitResult.CONTINUE;
551
            }
555
            }
552
 
556
 
553
            @Override
557
            @Override
554
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
558
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
555
                // The directory is now empty so delete it
559
                // The directory is now empty so delete it
556
                Files.delete(dir);
560
                Files.delete(dir);
557
                return FileVisitResult.CONTINUE;
561
                return FileVisitResult.CONTINUE;
558
            }
562
            }
559
        });
563
        });
560
    }
564
    }
561
 
565
 
562
    public static void rm(File f) throws IOException {
566
    public static void rm(File f) throws IOException {
563
        if (f.exists() && !f.delete())
567
        if (f.exists() && !f.delete())
564
            throw new IOException("cannot delete " + f);
568
            throw new IOException("cannot delete " + f);
565
    }
569
    }
566
 
570
 
567
    public static final File mkdir_p(File dir) throws IOException {
571
    public static final File mkdir_p(File dir) throws IOException {
568
        if (!dir.exists()) {
572
        if (!dir.exists()) {
569
            if (!dir.mkdirs()) {
573
            if (!dir.mkdirs()) {
570
                throw new IOException("cannot create directory " + dir);
574
                throw new IOException("cannot create directory " + dir);
571
            }
575
            }
572
        }
576
        }
573
        return dir;
577
        return dir;
574
    }
578
    }
575
 
579
 
576
    /**
580
    /**
577
     * Create all ancestors of <code>f</code>.
581
     * Create all ancestors of <code>f</code>.
578
     * 
582
     * 
579
     * @param f any file whose ancestors should be created.
583
     * @param f any file whose ancestors should be created.
580
     * @return <code>f</code>.
584
     * @return <code>f</code>.
581
     * @throws IOException if ancestors cannot be created.
585
     * @throws IOException if ancestors cannot be created.
582
     */
586
     */
583
    public static final File mkParentDirs(File f) throws IOException {
587
    public static final File mkParentDirs(File f) throws IOException {
584
        final File parentFile = f.getParentFile();
588
        final File parentFile = f.getParentFile();
585
        if (parentFile != null)
589
        if (parentFile != null)
586
            mkdir_p(parentFile);
590
            mkdir_p(parentFile);
587
        return f;
591
        return f;
588
    }
592
    }
589
 
593
 
590
    // **io
594
    // **io
591
 
595
 
592
    /**
596
    /**
593
     * Read a file line by line with the default encoding and returns the concatenation of these.
597
     * Read a file line by line with the default encoding and returns the concatenation of these.
594
     * 
598
     * 
595
     * @param f the file to read.
599
     * @param f the file to read.
596
     * @return the content of f.
600
     * @return the content of f.
597
     * @throws IOException if a pb occur while reading.
601
     * @throws IOException if a pb occur while reading.
598
     */
602
     */
599
    public static final String read(File f) throws IOException {
603
    public static final String read(File f) throws IOException {
600
        return read(new InputStreamReader(new FileInputStream(f)));
604
        return read(new InputStreamReader(new FileInputStream(f)));
601
    }
605
    }
602
 
606
 
603
    /**
607
    /**
604
     * Read a file line by line and returns the concatenation of these.
608
     * Read a file line by line and returns the concatenation of these.
605
     * 
609
     * 
606
     * @param f the file to read.
610
     * @param f the file to read.
607
     * @param charset the encoding of <code>f</code>.
611
     * @param charset the encoding of <code>f</code>.
608
     * @return the content of f.
612
     * @return the content of f.
609
     * @throws IOException if a pb occur while reading.
613
     * @throws IOException if a pb occur while reading.
610
     */
614
     */
611
    public static final String read(File f, String charset) throws IOException {
615
    public static final String read(File f, String charset) throws IOException {
612
        return read(new InputStreamReader(new FileInputStream(f), charset));
616
        return read(new InputStreamReader(new FileInputStream(f), charset));
613
    }
617
    }
614
 
618
 
615
    public static final String readUTF8(File f) throws IOException {
619
    public static final String readUTF8(File f) throws IOException {
616
        return readUTF8(new FileInputStream(f));
620
        return readUTF8(new FileInputStream(f));
617
    }
621
    }
618
 
622
 
-
 
623
    public static final String readUTF8(Path p) throws IOException {
-
 
624
        return new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
-
 
625
    }
-
 
626
 
619
    public static final String readUTF8(InputStream ins) throws IOException {
627
    public static final String readUTF8(InputStream ins) throws IOException {
620
        return read(ins, StringUtils.UTF8);
628
        return read(ins, StringUtils.UTF8);
621
    }
629
    }
622
 
630
 
623
    public static final String read(File f, Charset charset) throws IOException {
631
    public static final String read(File f, Charset charset) throws IOException {
624
        return read(new FileInputStream(f), charset);
632
        return read(new FileInputStream(f), charset);
625
    }
633
    }
626
 
634
 
627
    public static final String read(InputStream ins, Charset charset) throws IOException {
635
    public static final String read(InputStream ins, Charset charset) throws IOException {
628
        final Reader reader;
636
        final Reader reader;
629
        if (charset == null)
637
        if (charset == null)
630
            reader = new InputStreamReader(ins);
638
            reader = new InputStreamReader(ins);
631
        else
639
        else
632
            reader = new InputStreamReader(ins, charset);
640
            reader = new InputStreamReader(ins, charset);
633
        return read(reader);
641
        return read(reader);
634
    }
642
    }
635
 
643
 
636
    public static final String read(final Reader reader) throws IOException {
644
    public static final String read(final Reader reader) throws IOException {
637
        return read(reader, 8192);
645
        return read(reader, 8192);
638
    }
646
    }
639
 
647
 
640
    public static final String read(final Reader reader, final int bufferSize) throws IOException {
648
    public static final String read(final Reader reader, final int bufferSize) throws IOException {
641
        final StringBuilder sb = new StringBuilder();
649
        final StringBuilder sb = new StringBuilder();
642
        final char[] buffer = new char[bufferSize];
650
        final char[] buffer = new char[bufferSize];
643
        final BufferedReader in = new BufferedReader(reader);
651
        final BufferedReader in = new BufferedReader(reader);
644
        try {
652
        try {
645
            while (true) {
653
            while (true) {
646
                final int count = in.read(buffer);
654
                final int count = in.read(buffer);
647
                if (count == -1)
655
                if (count == -1)
648
                    break;
656
                    break;
649
                sb.append(buffer, 0, count);
657
                sb.append(buffer, 0, count);
650
            }
658
            }
651
        } finally {
659
        } finally {
652
            in.close();
660
            in.close();
653
        }
661
        }
654
        return sb.toString();
662
        return sb.toString();
655
    }
663
    }
656
 
664
 
657
    /**
665
    /**
658
     * Read the whole content of a file.
666
     * Read the whole content of a file.
659
     * 
667
     * 
660
     * @param f the file to read.
668
     * @param f the file to read.
661
     * @return its content.
669
     * @return its content.
662
     * @throws IOException if a pb occur while reading.
670
     * @throws IOException if a pb occur while reading.
663
     * @see Files#readAllBytes(java.nio.file.Path)
671
     * @see Files#readAllBytes(java.nio.file.Path)
664
     */
672
     */
665
    public static final byte[] readBytes(File f) throws IOException {
673
    public static final byte[] readBytes(File f) throws IOException {
666
        // works for /proc files which report 0 size
674
        // works for /proc files which report 0 size
667
        return Files.readAllBytes(f.toPath());
675
        return Files.readAllBytes(f.toPath());
668
    }
676
    }
669
 
677
 
670
    public static void write(String s, File f) throws IOException {
678
    public static void write(String s, File f) throws IOException {
671
        write(s, f, null, false);
679
        write(s, f, null, false);
672
    }
680
    }
673
 
681
 
674
    public static void write(String s, File f, Charset charset, boolean append) throws IOException {
682
    public static void write(String s, File f, Charset charset, boolean append) throws IOException {
675
        try (final FileOutputStream fileStream = new FileOutputStream(f, append);
683
        try (final FileOutputStream fileStream = new FileOutputStream(f, append);
676
                final BufferedWriter w = new BufferedWriter(charset == null ? new OutputStreamWriter(fileStream) : new OutputStreamWriter(fileStream, charset))) {
684
                final BufferedWriter w = new BufferedWriter(charset == null ? new OutputStreamWriter(fileStream) : new OutputStreamWriter(fileStream, charset))) {
677
            w.write(s);
685
            w.write(s);
678
        }
686
        }
679
    }
687
    }
680
 
688
 
681
    /**
689
    /**
682
     * Create a writer for the passed file, and write the XML declaration.
690
     * Create a writer for the passed file, and write the XML declaration.
683
     * 
691
     * 
684
     * @param f a file
692
     * @param f a file
685
     * @return a writer with the same encoding as the XML.
693
     * @return a writer with the same encoding as the XML.
686
     * @throws IOException if an error occurs.
694
     * @throws IOException if an error occurs.
687
     * @see StreamUtils#createXMLWriter(java.io.OutputStream)
695
     * @see StreamUtils#createXMLWriter(java.io.OutputStream)
688
     */
696
     */
689
    public static BufferedWriter createXMLWriter(final File f) throws IOException {
697
    public static BufferedWriter createXMLWriter(final File f) throws IOException {
690
        final FileOutputStream outs = new FileOutputStream(f);
698
        final FileOutputStream outs = new FileOutputStream(f);
691
        try {
699
        try {
692
            return StreamUtils.createXMLWriter(outs);
700
            return StreamUtils.createXMLWriter(outs);
693
        } catch (RuntimeException e) {
701
        } catch (RuntimeException e) {
694
            outs.close();
702
            outs.close();
695
            throw e;
703
            throw e;
696
        } catch (IOException e) {
704
        } catch (IOException e) {
697
            outs.close();
705
            outs.close();
698
            throw e;
706
            throw e;
699
        }
707
        }
700
    }
708
    }
701
 
709
 
702
    /**
710
    /**
703
     * Create an UTF-8 buffered writer.
711
     * Create an UTF-8 buffered writer.
704
     * 
712
     * 
705
     * @param f the file to write to.
713
     * @param f the file to write to.
706
     * @return a buffered writer.
714
     * @return a buffered writer.
707
     * @throws FileNotFoundException if the file cannot be opened.
715
     * @throws FileNotFoundException if the file cannot be opened.
708
     */
716
     */
709
    public static BufferedWriter createWriter(final File f) throws FileNotFoundException {
717
    public static BufferedWriter createWriter(final File f) throws FileNotFoundException {
710
        return createWriter(f, StringUtils.UTF8);
718
        return createWriter(f, StringUtils.UTF8);
711
    }
719
    }
712
 
720
 
713
    public static BufferedWriter createWriter(final File f, final Charset cs) throws FileNotFoundException {
721
    public static BufferedWriter createWriter(final File f, final Charset cs) throws FileNotFoundException {
714
        final FileOutputStream outs = new FileOutputStream(f);
722
        final FileOutputStream outs = new FileOutputStream(f);
715
        try {
723
        try {
716
            return new BufferedWriter(new OutputStreamWriter(outs, cs));
724
            return new BufferedWriter(new OutputStreamWriter(outs, cs));
717
        } catch (RuntimeException e) {
725
        } catch (RuntimeException e) {
718
            try {
726
            try {
719
                outs.close();
727
                outs.close();
720
            } catch (IOException e1) {
728
            } catch (IOException e1) {
721
                e1.printStackTrace();
729
                e1.printStackTrace();
722
            }
730
            }
723
            throw e;
731
            throw e;
724
        }
732
        }
725
    }
733
    }
726
 
734
 
727
    /**
735
    /**
728
     * Execute the passed transformer with the lock on the passed file.
736
     * Execute the passed transformer with the lock on the passed file.
729
     * 
737
     * 
730
     * @param <T> return type.
738
     * @param <T> return type.
731
     * @param f the file to lock.
739
     * @param f the file to lock.
732
     * @param transf what to do on the file.
740
     * @param transf what to do on the file.
733
     * @return what <code>transf</code> returns.
741
     * @return what <code>transf</code> returns.
734
     * @throws IOException if an error occurs while locking the file.
742
     * @throws IOException if an error occurs while locking the file.
735
     * @throws X if an error occurs while using the file.
743
     * @throws X if an error occurs while using the file.
736
     */
744
     */
737
    public static final <T, X extends Exception> T doWithLock(final File f, ExnTransformer<RandomAccessFile, T, X> transf) throws IOException, X {
745
    public static final <T, X extends Exception> T doWithLock(final File f, ExnTransformer<RandomAccessFile, T, X> transf) throws IOException, X {
738
        mkParentDirs(f);
746
        mkParentDirs(f);
739
        // don't use FileOutputStream : it truncates the file on creation
747
        // don't use FileOutputStream : it truncates the file on creation
740
        // we need write to obtain lock
748
        // we need write to obtain lock
741
        try (final RandomAccessFile out = new RandomAccessFile(f, "rw")) {
749
        try (final RandomAccessFile out = new RandomAccessFile(f, "rw")) {
742
            out.getChannel().lock();
750
            out.getChannel().lock();
743
            return transf.transformChecked(out);
751
            return transf.transformChecked(out);
744
        }
752
        }
745
        // the lock is released on close()
753
        // the lock is released on close()
746
    }
754
    }
747
 
755
 
748
    private static final Map<URL, File> files = new HashMap<URL, File>();
756
    private static final Map<URL, File> files = new HashMap<URL, File>();
749
 
757
 
750
    private static final File getShortCutFile() throws IOException {
758
    private static final File getShortCutFile() throws IOException {
751
        return getFile(FileUtils.class.getResource("shortcut.vbs"));
759
        return getFile(FileUtils.class.getResource("shortcut.vbs"));
752
    }
760
    }
753
 
761
 
754
    // windows cannot execute a string, it demands a file
762
    // windows cannot execute a string, it demands a file
755
    public static final File getFile(final URL url) throws IOException {
763
    public static final File getFile(final URL url) throws IOException {
756
        // avoid unnecessary IO if already a file
764
        // avoid unnecessary IO if already a file
757
        File urlFile = null;
765
        File urlFile = null;
758
        // inexpensive comparison before trying to convert to URI and call the File constructor
766
        // inexpensive comparison before trying to convert to URI and call the File constructor
759
        if ("file".equalsIgnoreCase(url.getProtocol())) {
767
        if ("file".equalsIgnoreCase(url.getProtocol())) {
760
            try {
768
            try {
761
                urlFile = new File(url.toURI());
769
                urlFile = new File(url.toURI());
762
            } catch (Exception e) {
770
            } catch (Exception e) {
763
                Log.get().log(Level.FINER, "couldn't convert to file " + url, e);
771
                Log.get().log(Level.FINER, "couldn't convert to file " + url, e);
764
            }
772
            }
765
        }
773
        }
766
        if (urlFile != null)
774
        if (urlFile != null)
767
            return urlFile;
775
            return urlFile;
768
 
776
 
769
        final File shortcutFile;
777
        final File shortcutFile;
770
        final File currentFile = files.get(url);
778
        final File currentFile = files.get(url);
771
        if (currentFile == null || !currentFile.exists()) {
779
        if (currentFile == null || !currentFile.exists()) {
772
            shortcutFile = File.createTempFile("windowsIsLame", ".vbs");
780
            shortcutFile = File.createTempFile("windowsIsLame", ".vbs");
773
            // ATTN if the VM is not terminated normally, the file won't be deleted
781
            // ATTN if the VM is not terminated normally, the file won't be deleted
774
            // perhaps a thread to delete the file after a certain amount of time
782
            // perhaps a thread to delete the file after a certain amount of time
775
            shortcutFile.deleteOnExit();
783
            shortcutFile.deleteOnExit();
776
            files.put(url, shortcutFile);
784
            files.put(url, shortcutFile);
777
            try (final InputStream stream = url.openStream(); final FileOutputStream out = new FileOutputStream(shortcutFile);) {
785
            try (final InputStream stream = url.openStream(); final FileOutputStream out = new FileOutputStream(shortcutFile);) {
778
                StreamUtils.copy(stream, out);
786
                StreamUtils.copy(stream, out);
779
            }
787
            }
780
        } else
788
        } else
781
            shortcutFile = currentFile;
789
            shortcutFile = currentFile;
782
        return shortcutFile;
790
        return shortcutFile;
783
    }
791
    }
784
 
792
 
785
    /**
793
    /**
786
     * Create a symbolic link from <code>link</code> to <code>target</code>.
794
     * Create a symbolic link from <code>link</code> to <code>target</code>.
787
     * 
795
     * 
788
     * @param target the target of the link, eg ".".
796
     * @param target the target of the link, eg ".".
789
     * @param link the file to create or replace, eg "l".
797
     * @param link the file to create or replace, eg "l".
790
     * @return the link if the creation was successfull, <code>null</code> otherwise, eg "l.LNK".
798
     * @return the link if the creation was successfull, <code>null</code> otherwise, eg "l.LNK".
791
     * @throws IOException if an error occurs.
799
     * @throws IOException if an error occurs.
792
     */
800
     */
793
    public static final File ln(final File target, final File link) throws IOException {
801
    public static final File ln(final File target, final File link) throws IOException {
794
        final Process ps;
802
        final Process ps;
795
        final File res;
803
        final File res;
796
        if (OSFamily.getInstance() == OSFamily.Windows) {
804
        if (OSFamily.getInstance() == OSFamily.Windows) {
797
            // using the .vbs since it doesn't depends on cygwin
805
            // using the .vbs since it doesn't depends on cygwin
798
            // and cygwin's ln is weird :
806
            // and cygwin's ln is weird :
799
            // 1. needs CYGWIN=winsymlinks to create a shortcut, but even then "ln -f" doesn't work
807
            // 1. needs CYGWIN=winsymlinks to create a shortcut, but even then "ln -f" doesn't work
800
            // since it tries to delete l instead of l.LNK
808
            // since it tries to delete l instead of l.LNK
801
            // 2. it sets the system flag so "dir" doesn't show the shortcut (unless you add /AS)
809
            // 2. it sets the system flag so "dir" doesn't show the shortcut (unless you add /AS)
802
            // 3. the shortcut is recognized as a symlink thanks to a special attribute that can get
810
            // 3. the shortcut is recognized as a symlink thanks to a special attribute that can get
803
            // lost (e.g. copying in eclipse)
811
            // lost (e.g. copying in eclipse)
804
            ps = Runtime.getRuntime().exec(new String[] { "cscript", getShortCutFile().getAbsolutePath(), link.getAbsolutePath(), target.getCanonicalPath() });
812
            ps = Runtime.getRuntime().exec(new String[] { "cscript", getShortCutFile().getAbsolutePath(), link.getAbsolutePath(), target.getCanonicalPath() });
805
            res = new File(link.getParentFile(), link.getName() + ".LNK");
813
            res = new File(link.getParentFile(), link.getName() + ".LNK");
806
        } else {
814
        } else {
807
            final String rel = FileUtils.relative(link.getAbsoluteFile().getParentFile(), target);
815
            final String rel = FileUtils.relative(link.getAbsoluteFile().getParentFile(), target);
808
            // add -f to replace existing links
816
            // add -f to replace existing links
809
            // add -n so that ln -sf aDir anExistantLinkToIt succeed
817
            // add -n so that ln -sf aDir anExistantLinkToIt succeed
810
            final String[] cmdarray = { "ln", "-sfn", rel, link.getAbsolutePath() };
818
            final String[] cmdarray = { "ln", "-sfn", rel, link.getAbsolutePath() };
811
            ps = Runtime.getRuntime().exec(cmdarray);
819
            ps = Runtime.getRuntime().exec(cmdarray);
812
            res = link;
820
            res = link;
813
        }
821
        }
814
        // no need for output, either it succeeds or it fails
822
        // no need for output, either it succeeds or it fails
815
        ProcessStreams.handle(ps, Action.CLOSE);
823
        ProcessStreams.handle(ps, Action.CLOSE);
816
        try {
824
        try {
817
            final int exitValue = ps.waitFor();
825
            final int exitValue = ps.waitFor();
818
            if (exitValue == 0)
826
            if (exitValue == 0)
819
                return res;
827
                return res;
820
            else
828
            else
821
                throw new IOException("Abnormal exit value: " + exitValue);
829
                throw new IOException("Abnormal exit value: " + exitValue);
822
        } catch (InterruptedException e) {
830
        } catch (InterruptedException e) {
823
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
831
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
824
        }
832
        }
825
    }
833
    }
826
 
834
 
827
    /**
835
    /**
828
     * Resolve a symbolic link or a windows shortcut.
836
     * Resolve a symbolic link or a windows shortcut.
829
     * 
837
     * 
830
     * @param link the shortcut, e.g. shortcut.lnk.
838
     * @param link the shortcut, e.g. shortcut.lnk.
831
     * @return the target of <code>link</code>, <code>null</code> if not found, e.g. target.txt.
839
     * @return the target of <code>link</code>, <code>null</code> if not found, e.g. target.txt.
832
     * @throws IOException if an error occurs.
840
     * @throws IOException if an error occurs.
833
     */
841
     */
834
    public static final File readlink(final File link) throws IOException {
842
    public static final File readlink(final File link) throws IOException {
835
        final Process ps;
843
        final Process ps;
836
        if (OSFamily.getInstance() == OSFamily.Windows) {
844
        if (OSFamily.getInstance() == OSFamily.Windows) {
837
            ps = Runtime.getRuntime().exec(new String[] { "cscript", "//NoLogo", getShortCutFile().getAbsolutePath(), link.getAbsolutePath() });
845
            ps = Runtime.getRuntime().exec(new String[] { "cscript", "//NoLogo", getShortCutFile().getAbsolutePath(), link.getAbsolutePath() });
838
        } else {
846
        } else {
839
            // add -f to canonicalize
847
            // add -f to canonicalize
840
            ps = Runtime.getRuntime().exec(new String[] { "readlink", "-f", link.getAbsolutePath() });
848
            ps = Runtime.getRuntime().exec(new String[] { "readlink", "-f", link.getAbsolutePath() });
841
        }
849
        }
842
        try {
850
        try {
843
            ps.getErrorStream().close();
851
            ps.getErrorStream().close();
844
            final String res;
852
            final String res;
845
            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(ps.getInputStream()));) {
853
            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(ps.getInputStream()));) {
846
                res = reader.readLine();
854
                res = reader.readLine();
847
            }
855
            }
848
            if (ps.waitFor() != 0 || res == null || res.length() == 0)
856
            if (ps.waitFor() != 0 || res == null || res.length() == 0)
849
                return null;
857
                return null;
850
            else
858
            else
851
                return new File(res);
859
                return new File(res);
852
        } catch (InterruptedException e) {
860
        } catch (InterruptedException e) {
853
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
861
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
854
        }
862
        }
855
    }
863
    }
856
 
864
 
857
    // from guava/src/com/google/common/io/Files.java
865
    // from guava/src/com/google/common/io/Files.java
858
    /** Maximum loop count when creating temp directories. */
866
    /** Maximum loop count when creating temp directories. */
859
    private static final int TEMP_DIR_ATTEMPTS = 10000;
867
    private static final int TEMP_DIR_ATTEMPTS = 10000;
860
 
868
 
861
    /**
869
    /**
862
     * Atomically creates a new directory somewhere beneath the system's temporary directory (as
870
     * Atomically creates a new directory somewhere beneath the system's temporary directory (as
863
     * defined by the {@code java.io.tmpdir} system property), and returns its name.
871
     * defined by the {@code java.io.tmpdir} system property), and returns its name.
864
     * 
872
     * 
865
     * <p>
873
     * <p>
866
     * Use this method instead of {@link File#createTempFile(String, String)} when you wish to
874
     * Use this method instead of {@link File#createTempFile(String, String)} when you wish to
867
     * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
875
     * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
868
     * delete the file and create a directory in its place, but this leads a race condition which
876
     * delete the file and create a directory in its place, but this leads a race condition which
869
     * can be exploited to create security vulnerabilities, especially when executable files are to
877
     * can be exploited to create security vulnerabilities, especially when executable files are to
870
     * be written into the directory.
878
     * be written into the directory.
871
     * 
879
     * 
872
     * <p>
880
     * <p>
873
     * This method assumes that the temporary volume is writable, has free inodes and free blocks,
881
     * This method assumes that the temporary volume is writable, has free inodes and free blocks,
874
     * and that it will not be called thousands of times per second.
882
     * and that it will not be called thousands of times per second.
875
     * 
883
     * 
876
     * @param prefix the prefix string to be used in generating the directory's name.
884
     * @param prefix the prefix string to be used in generating the directory's name.
877
     * @return the newly-created directory.
885
     * @return the newly-created directory.
878
     * @throws IllegalStateException if the directory could not be created.
886
     * @throws IllegalStateException if the directory could not be created.
879
     */
887
     */
880
    public static File createTempDir(final String prefix) {
888
    public static File createTempDir(final String prefix) {
881
        final File baseDir = new File(System.getProperty("java.io.tmpdir"));
889
        final File baseDir = new File(System.getProperty("java.io.tmpdir"));
882
        final String baseName = prefix + System.currentTimeMillis() + "-";
890
        final String baseName = prefix + System.currentTimeMillis() + "-";
883
 
891
 
884
        for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
892
        for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
885
            final File tempDir = new File(baseDir, baseName + counter);
893
            final File tempDir = new File(baseDir, baseName + counter);
886
            if (tempDir.mkdir()) {
894
            if (tempDir.mkdir()) {
887
                return tempDir;
895
                return tempDir;
888
            }
896
            }
889
        }
897
        }
890
        throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
898
        throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
891
    }
899
    }
892
 
900
 
893
    /**
901
    /**
894
     * Tries to open the passed file as if it were graphically opened by the current user (respect
902
     * Tries to open the passed file as if it were graphically opened by the current user (respect
895
     * user's "open with"). If a native way to open the file can't be found, tries the passed list
903
     * user's "open with"). If a native way to open the file can't be found, tries the passed list
896
     * of executables.
904
     * of executables.
897
     * 
905
     * 
898
     * @param f the file to open.
906
     * @param f the file to open.
899
     * @param executables a list of executables to try, e.g. ["ooffice", "soffice"].
907
     * @param executables a list of executables to try, e.g. ["ooffice", "soffice"].
900
     * @throws IOException if the file can't be opened.
908
     * @throws IOException if the file can't be opened.
901
     */
909
     */
902
    public static final void open(File f, String[] executables) throws IOException {
910
    public static final void open(File f, String[] executables) throws IOException {
-
 
911
        if (!f.exists()) {
-
 
912
            throw new FileNotFoundException(f.getAbsolutePath() + " not found");
-
 
913
        }
903
        try {
914
        try {
904
            openNative(f);
915
            openNative(f);
905
        } catch (IOException exn) {
916
        } catch (IOException exn) {
906
            for (int i = 0; i < executables.length; i++) {
917
            for (int i = 0; i < executables.length; i++) {
907
                final String executable = executables[i];
918
                final String executable = executables[i];
908
                try {
919
                try {
909
                    ProcessStreams.handle(Runtime.getRuntime().exec(new String[] { executable, f.getCanonicalPath() }), Action.CLOSE);
920
                    ProcessStreams.handle(Runtime.getRuntime().exec(new String[] { executable, f.getCanonicalPath() }), Action.CLOSE);
910
                    return;
921
                    return;
911
                } catch (IOException e) {
922
                } catch (IOException e) {
912
                    // try the next one
923
                    // try the next one
913
                }
924
                }
914
            }
925
            }
915
            throw ExceptionUtils.createExn(IOException.class, "unable to open " + f + " with: " + Arrays.asList(executables), exn);
926
            throw ExceptionUtils.createExn(IOException.class, "unable to open " + f + " with: " + Arrays.asList(executables), exn);
916
        }
927
        }
917
    }
928
    }
918
 
929
 
919
    /**
930
    /**
920
     * Open the passed file as if it were graphically opened by the current user (user's "open
931
     * Open the passed file as if it were graphically opened by the current user (user's "open
921
     * with").
932
     * with").
922
     * 
933
     * 
923
     * @param f the file to open.
934
     * @param f the file to open.
924
     * @throws IOException if f couldn't be opened.
935
     * @throws IOException if f couldn't be opened.
925
     */
936
     */
926
    private static final void openNative(File f) throws IOException {
937
    private static final void openNative(File f) throws IOException {
927
        final OSFamily os = OSFamily.getInstance();
938
        final OSFamily os = OSFamily.getInstance();
928
        final String[] cmdarray;
939
        final String[] cmdarray;
929
        if (os == OSFamily.Windows) {
940
        if (os == OSFamily.Windows) {
930
            cmdarray = new String[] { "cmd", "/c", "start", "\"\"", f.getCanonicalPath() };
941
            cmdarray = new String[] { "cmd", "/c", "start", "\"\"", f.getCanonicalPath() };
931
        } else if (os == OSFamily.Mac) {
942
        } else if (os == OSFamily.Mac) {
932
            cmdarray = new String[] { "open", f.getCanonicalPath() };
943
            cmdarray = new String[] { "open", f.getCanonicalPath() };
933
        } else if (os instanceof Unix) {
944
        } else if (os instanceof Unix) {
934
            cmdarray = new String[] { "xdg-open", f.getCanonicalPath() };
945
            cmdarray = new String[] { "xdg-open", f.getCanonicalPath() };
935
        } else {
946
        } else {
936
            throw new IOException("unknown way to open " + f);
947
            throw new IOException("unknown way to open " + f);
937
        }
948
        }
938
        try {
949
        try {
939
            final Process ps = Runtime.getRuntime().exec(cmdarray);
950
            final Process ps = Runtime.getRuntime().exec(cmdarray);
940
            ProcessStreams.handle(ps, Action.CLOSE);
951
            ProcessStreams.handle(ps, Action.CLOSE);
941
            // can wait since the command return as soon as the native application is launched
952
            // can wait since the command return as soon as the native application is launched
942
            // (i.e. this won't wait 30s for OpenOffice)
953
            // (i.e. this won't wait 30s for OpenOffice)
943
            final int res = ps.waitFor();
954
            final int res = ps.waitFor();
944
            if (res != 0)
955
            if (res != 0)
945
                throw new IOException("error (" + res + ") executing " + Arrays.asList(cmdarray));
956
                throw new IOException("error (" + res + ") executing " + Arrays.asList(cmdarray));
946
        } catch (InterruptedException e) {
957
        } catch (InterruptedException e) {
947
            throw ExceptionUtils.createExn(IOException.class, "interrupted waiting for " + Arrays.asList(cmdarray), e);
958
            throw ExceptionUtils.createExn(IOException.class, "interrupted waiting for " + Arrays.asList(cmdarray), e);
948
        }
959
        }
949
    }
960
    }
950
 
961
 
951
    static final boolean gnomeRunning() {
962
    static final boolean gnomeRunning() {
952
        try {
963
        try {
953
            final Process ps = Runtime.getRuntime().exec(new String[] { "pgrep", "-u", System.getProperty("user.name"), "nautilus" });
964
            final Process ps = Runtime.getRuntime().exec(new String[] { "pgrep", "-u", System.getProperty("user.name"), "nautilus" });
954
            // no need for output, use exit status
965
            // no need for output, use exit status
955
            ProcessStreams.handle(ps, Action.CLOSE);
966
            ProcessStreams.handle(ps, Action.CLOSE);
956
            return ps.waitFor() == 0;
967
            return ps.waitFor() == 0;
957
        } catch (Exception e) {
968
        } catch (Exception e) {
958
            return false;
969
            return false;
959
        }
970
        }
960
    }
971
    }
961
 
972
 
962
    public static final String XML_TYPE = "text/xml";
973
    public static final String XML_TYPE = "text/xml";
963
    private static final Map<String, String> ext2mime;
974
    private static final Map<String, String> ext2mime;
964
    private static final SetMap<String, String> mime2ext;
975
    private static final SetMap<String, String> mime2ext;
965
 
976
 
966
    static {
977
    static {
967
        mime2ext = new SetMap<String, String>(Mode.NULL_FORBIDDEN);
978
        mime2ext = new SetMap<String, String>(Mode.NULL_FORBIDDEN);
968
        mime2ext.putCollection(XML_TYPE, ".xml");
979
        mime2ext.putCollection(XML_TYPE, ".xml");
969
        mime2ext.putCollection("image/jpeg", ".jpg", ".jpeg");
980
        mime2ext.putCollection("image/jpeg", ".jpg", ".jpeg");
970
        mime2ext.putCollection("image/png", ".png");
981
        mime2ext.putCollection("image/png", ".png");
971
        mime2ext.putCollection("image/tiff", ".tiff", ".tif");
982
        mime2ext.putCollection("image/tiff", ".tiff", ".tif");
972
        mime2ext.putCollection("application/pdf", ".pdf");
983
        mime2ext.putCollection("application/pdf", ".pdf");
973
 
984
 
974
        mime2ext.putCollection("application/vnd.oasis.opendocument.spreadsheet", ".ods");
985
        mime2ext.putCollection("application/vnd.oasis.opendocument.spreadsheet", ".ods");
975
        mime2ext.putCollection("application/vnd.oasis.opendocument.text", ".odt");
986
        mime2ext.putCollection("application/vnd.oasis.opendocument.text", ".odt");
976
        mime2ext.putCollection("application/vnd.oasis.opendocument.presentation", ".odp");
987
        mime2ext.putCollection("application/vnd.oasis.opendocument.presentation", ".odp");
977
        mime2ext.putCollection("application/vnd.oasis.opendocument.graphics", ".odg");
988
        mime2ext.putCollection("application/vnd.oasis.opendocument.graphics", ".odg");
978
 
989
 
979
        ext2mime = new HashMap<String, String>();
990
        ext2mime = new HashMap<String, String>();
980
        for (final Entry<String, Set<String>> e : mime2ext.entrySet()) {
991
        for (final Entry<String, Set<String>> e : mime2ext.entrySet()) {
981
            final String m = e.getKey();
992
            final String m = e.getKey();
982
            for (final String ext : e.getValue()) {
993
            for (final String ext : e.getValue()) {
983
                if (ext2mime.put(ext, m) != null)
994
                if (ext2mime.put(ext, m) != null)
984
                    Log.get().info("Duplicate extension : " + ext);
995
                    Log.get().info("Duplicate extension : " + ext);
985
            }
996
            }
986
        }
997
        }
987
    }
998
    }
988
 
999
 
989
    /**
1000
    /**
990
     * Try to guess the media type of the passed file name (see
1001
     * Try to guess the media type of the passed file name (see
991
     * <a href="http://www.iana.org/assignments/media-types">iana</a>).
1002
     * <a href="http://www.iana.org/assignments/media-types">iana</a>).
992
     * 
1003
     * 
993
     * @param fname a file name.
1004
     * @param fname a file name.
994
     * @return its mime type.
1005
     * @return its mime type.
995
     */
1006
     */
996
    public static final String findMimeType(String fname) {
1007
    public static final String findMimeType(String fname) {
997
        for (final Map.Entry<String, String> e : ext2mime.entrySet()) {
1008
        for (final Map.Entry<String, String> e : ext2mime.entrySet()) {
998
            if (fname.toLowerCase().endsWith(e.getKey()))
1009
            if (fname.toLowerCase().endsWith(e.getKey()))
999
                return e.getValue();
1010
                return e.getValue();
1000
        }
1011
        }
1001
        return null;
1012
        return null;
1002
    }
1013
    }
1003
 
1014
 
1004
    public static final Set<String> getExtensionsFromMimeType(final String mimetype) {
1015
    public static final Set<String> getExtensionsFromMimeType(final String mimetype) {
1005
        return mime2ext.get(mimetype);
1016
        return mime2ext.get(mimetype);
1006
    }
1017
    }
1007
 
1018
 
1008
    /**
1019
    /**
1009
     * Return the string after the last dot.
1020
     * Return the string after the last dot.
1010
     * 
1021
     * 
1011
     * @param fname a name, e.g. "test.odt" or "sans".
1022
     * @param fname a name, e.g. "test.odt" or "sans".
1012
     * @return the extension, e.g. "odt" or <code>null</code>.
1023
     * @return the extension, e.g. "odt" or <code>null</code>.
1013
     */
1024
     */
1014
    public static final String getExtension(String fname) {
1025
    public static final String getExtension(String fname) {
1015
        return getExtension(fname, false);
1026
        return getExtension(fname, false);
1016
    }
1027
    }
1017
 
1028
 
1018
    public static final String getExtension(final String fname, final boolean withDot) {
1029
    public static final String getExtension(final String fname, final boolean withDot) {
1019
        final int lastIndex = fname.lastIndexOf('.');
1030
        final int lastIndex = fname.lastIndexOf('.');
1020
        return lastIndex < 0 ? null : fname.substring(lastIndex + (withDot ? 0 : 1));
1031
        return lastIndex < 0 ? null : fname.substring(lastIndex + (withDot ? 0 : 1));
1021
    }
1032
    }
1022
 
1033
 
1023
    /**
1034
    /**
1024
     * Chars not valid in filenames.
1035
     * Chars not valid in filenames.
1025
     */
1036
     */
1026
    public static final Collection<Character> INVALID_CHARS;
1037
    public static final Collection<Character> INVALID_CHARS;
1027
 
1038
 
1028
    /**
1039
    /**
1029
     * An escaper suitable for producing valid filenames.
1040
     * An escaper suitable for producing valid filenames.
1030
     */
1041
     */
1031
    public static final Escaper FILENAME_ESCAPER = new StringUtils.Escaper('\'', 'Q');
1042
    public static final Escaper FILENAME_ESCAPER = new StringUtils.Escaper('\'', 'Q');
1032
 
1043
 
1033
    static private final String WS = "\\p{javaWhitespace}";
1044
    static private final String WS = "\\p{javaWhitespace}";
1034
    static private final Pattern WS_PATTERN = Pattern.compile(WS + "+");
1045
    static private final Pattern WS_PATTERN = Pattern.compile(WS + "+");
1035
    static private final Pattern CONTROL_PATTERN = Pattern.compile("[\\p{IsCc}\\p{IsCf}&&[^" + WS + "]]+");
1046
    static private final Pattern CONTROL_PATTERN = Pattern.compile("[\\p{IsCc}\\p{IsCf}&&[^" + WS + "]]+");
1036
    static private final Pattern INVALID_CHARS_PATTERN;
1047
    static private final Pattern INVALID_CHARS_PATTERN;
1037
 
1048
 
1038
    static {
1049
    static {
1039
        // from windows explorer
1050
        // from windows explorer
1040
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
1051
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
1041
        // Naming Files, Paths, and Namespaces
1052
        // Naming Files, Paths, and Namespaces
1042
        // on Mac only '/' and ':', on Linux only '/'
1053
        // on Mac only '/' and ':', on Linux only '/'
1043
        FILENAME_ESCAPER.add('"', 'D').add(':', 'C').add('/', 'S').add('\\', 'A');
1054
        FILENAME_ESCAPER.add('"', 'D').add(':', 'C').add('/', 'S').add('\\', 'A');
1044
        FILENAME_ESCAPER.add('<', 'L').add('>', 'G').add('*', 'R').add('|', 'P').add('?', 'M');
1055
        FILENAME_ESCAPER.add('<', 'L').add('>', 'G').add('*', 'R').add('|', 'P').add('?', 'M');
1045
        INVALID_CHARS = FILENAME_ESCAPER.getEscapedChars();
1056
        INVALID_CHARS = FILENAME_ESCAPER.getEscapedChars();
1046
        INVALID_CHARS_PATTERN = Pattern.compile("[" + CollectionUtils.join(INVALID_CHARS, "") + "]");
1057
        INVALID_CHARS_PATTERN = Pattern.compile("[" + CollectionUtils.join(INVALID_CHARS, "") + "]");
1047
    }
1058
    }
1048
 
1059
 
1049
    /**
1060
    /**
1050
     * Sanitize a name. Remove control characters, trim, and replace {@link #INVALID_CHARS} by '_'.
1061
     * Sanitize a name. Remove control characters, trim, and replace {@link #INVALID_CHARS} by '_'.
1051
     * 
1062
     * 
1052
     * @param name an arbitrary name.
1063
     * @param name an arbitrary name.
1053
     * @return a name suitable for any file system.
1064
     * @return a name suitable for any file system.
1054
     */
1065
     */
1055
    static public final String sanitize(String name) {
1066
    static public final String sanitize(String name) {
1056
        // remove control and format characters (except white spaces)
1067
        // remove control and format characters (except white spaces)
1057
        name = CONTROL_PATTERN.matcher(name).replaceAll("");
1068
        name = CONTROL_PATTERN.matcher(name).replaceAll("");
1058
        // only use one regular space (must be done after removing control characters as if they are
1069
        // only use one regular space (must be done after removing control characters as if they are
1059
        // between spaces we want only one space to remain)
1070
        // between spaces we want only one space to remain)
1060
        name = WS_PATTERN.matcher(name).replaceAll(" ");
1071
        name = WS_PATTERN.matcher(name).replaceAll(" ");
1061
        // leading and trailing spaces are hard to see (and illegal in Explorer)
1072
        // leading and trailing spaces are hard to see (and illegal in Explorer)
1062
        name = name.trim();
1073
        name = name.trim();
1063
 
1074
 
1064
        // replace all invalid characters with _
1075
        // replace all invalid characters with _
1065
        name = INVALID_CHARS_PATTERN.matcher(name).replaceAll("_");
1076
        name = INVALID_CHARS_PATTERN.matcher(name).replaceAll("_");
1066
 
1077
 
1067
        return name;
1078
        return name;
1068
    }
1079
    }
1069
 
1080
 
1070
    public static final FileFilter DIR_FILTER = new FileFilter() {
1081
    public static final FileFilter DIR_FILTER = new FileFilter() {
1071
        @Override
1082
        @Override
1072
        public boolean accept(File f) {
1083
        public boolean accept(File f) {
1073
            return f.isDirectory();
1084
            return f.isDirectory();
1074
        }
1085
        }
1075
    };
1086
    };
1076
    public static final FileFilter REGULAR_FILE_FILTER = new FileFilter() {
1087
    public static final FileFilter REGULAR_FILE_FILTER = new FileFilter() {
1077
        @Override
1088
        @Override
1078
        public boolean accept(File f) {
1089
        public boolean accept(File f) {
1079
            return f.isFile();
1090
            return f.isFile();
1080
        }
1091
        }
1081
    };
1092
    };
1082
    public static final Filter<Path> DIR_PATH_FILTER = new DirectoryStream.Filter<Path>() {
1093
    public static final Filter<Path> DIR_PATH_FILTER = new DirectoryStream.Filter<Path>() {
1083
        @Override
1094
        @Override
1084
        public boolean accept(Path entry) throws IOException {
1095
        public boolean accept(Path entry) throws IOException {
1085
            return Files.isDirectory(entry, LinkOption.NOFOLLOW_LINKS);
1096
            return Files.isDirectory(entry, LinkOption.NOFOLLOW_LINKS);
1086
        }
1097
        }
1087
    };
1098
    };
1088
 
1099
 
1089
    /**
1100
    /**
1090
     * Return a filter that select regular files ending in <code>ext</code>.
1101
     * Return a filter that select regular files ending in <code>ext</code>.
1091
     * 
1102
     * 
1092
     * @param ext the end of the name, eg ".xml".
1103
     * @param ext the end of the name, eg ".xml".
1093
     * @return the corresponding filter.
1104
     * @return the corresponding filter.
1094
     */
1105
     */
1095
    public static final FileFilter createEndFileFilter(final String ext) {
1106
    public static final FileFilter createEndFileFilter(final String ext) {
1096
        return new FileFilter() {
1107
        return new FileFilter() {
1097
            @Override
1108
            @Override
1098
            public boolean accept(File f) {
1109
            public boolean accept(File f) {
1099
                return f.isFile() && f.getName().endsWith(ext);
1110
                return f.isFile() && f.getName().endsWith(ext);
1100
            }
1111
            }
1101
        };
1112
        };
1102
    }
1113
    }
1103
 
1114
 
1104
    /**
1115
    /**
1105
     * How to merge the group and others portion of permissions for non-POSIX FS.
1116
     * How to merge the group and others portion of permissions for non-POSIX FS.
1106
     * 
1117
     * 
1107
     * @author sylvain
1118
     * @author sylvain
1108
     * @see FileUtils#setFilePermissionsFromPOSIX(File, String, GroupAndOthers)
1119
     * @see FileUtils#setFilePermissionsFromPOSIX(File, String, GroupAndOthers)
1109
     */
1120
     */
1110
    public static enum GroupAndOthers {
1121
    public static enum GroupAndOthers {
1111
        REQUIRE_SAME {
1122
        REQUIRE_SAME {
1112
            @Override
1123
            @Override
1113
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1124
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1114
                throw new IllegalArgumentException("Different permissions : " + groupPerms + " != " + otherPerms);
1125
                throw new IllegalArgumentException("Different permissions : " + groupPerms + " != " + otherPerms);
1115
            }
1126
            }
1116
        },
1127
        },
1117
        PERMISSIVE {
1128
        PERMISSIVE {
1118
            @Override
1129
            @Override
1119
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1130
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1120
                final EnumSet<Permission> res = EnumSet.noneOf(Permission.class);
1131
                final EnumSet<Permission> res = EnumSet.noneOf(Permission.class);
1121
                res.addAll(groupPerms);
1132
                res.addAll(groupPerms);
1122
                res.addAll(otherPerms);
1133
                res.addAll(otherPerms);
1123
                return res;
1134
                return res;
1124
            }
1135
            }
1125
        },
1136
        },
1126
        OTHERS {
1137
        OTHERS {
1127
            @Override
1138
            @Override
1128
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1139
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1129
                return otherPerms;
1140
                return otherPerms;
1130
            }
1141
            }
1131
        },
1142
        },
1132
        RESTRICTIVE {
1143
        RESTRICTIVE {
1133
            @Override
1144
            @Override
1134
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1145
            protected Set<Permission> getNonEqual(Set<Permission> groupPerms, Set<Permission> otherPerms) {
1135
                final EnumSet<Permission> res = EnumSet.allOf(Permission.class);
1146
                final EnumSet<Permission> res = EnumSet.allOf(Permission.class);
1136
                res.retainAll(groupPerms);
1147
                res.retainAll(groupPerms);
1137
                res.retainAll(otherPerms);
1148
                res.retainAll(otherPerms);
1138
                return res;
1149
                return res;
1139
            }
1150
            }
1140
        };
1151
        };
1141
 
1152
 
1142
        public final Set<Permission> getPermissions(final Set<Permission> groupPerms, final Set<Permission> otherPerms) {
1153
        public final Set<Permission> getPermissions(final Set<Permission> groupPerms, final Set<Permission> otherPerms) {
1143
            if (groupPerms.equals(otherPerms)) {
1154
            if (groupPerms.equals(otherPerms)) {
1144
                return groupPerms;
1155
                return groupPerms;
1145
            } else {
1156
            } else {
1146
                return getNonEqual(groupPerms, otherPerms);
1157
                return getNonEqual(groupPerms, otherPerms);
1147
            }
1158
            }
1148
        }
1159
        }
1149
 
1160
 
1150
        public final Set<Permission> getPermissions(final String posixPerms) {
1161
        public final Set<Permission> getPermissions(final String posixPerms) {
1151
            final Set<Permission> groupPerms = Permission.fromString(posixPerms.substring(3, 6));
1162
            final Set<Permission> groupPerms = Permission.fromString(posixPerms.substring(3, 6));
1152
            final Set<Permission> otherPerms = Permission.fromString(posixPerms.substring(6, 9));
1163
            final Set<Permission> otherPerms = Permission.fromString(posixPerms.substring(6, 9));
1153
            return this.getPermissions(groupPerms, otherPerms);
1164
            return this.getPermissions(groupPerms, otherPerms);
1154
        }
1165
        }
1155
 
1166
 
1156
        protected abstract Set<Permission> getNonEqual(final Set<Permission> groupPerms, final Set<Permission> otherPerms);
1167
        protected abstract Set<Permission> getNonEqual(final Set<Permission> groupPerms, final Set<Permission> otherPerms);
1157
 
1168
 
1158
    }
1169
    }
1159
 
1170
 
1160
    public static final String setPermissions(final Path p, final String posixPerms) throws IOException {
1171
    public static final String setPermissions(final Path p, final String posixPerms) throws IOException {
1161
        return setPermissions(p, posixPerms, GroupAndOthers.RESTRICTIVE);
1172
        return setPermissions(p, posixPerms, GroupAndOthers.RESTRICTIVE);
1162
    }
1173
    }
1163
 
1174
 
1164
    /**
1175
    /**
1165
     * Use {@link PosixFileAttributeView#setPermissions(Set)} if possible, otherwise use
1176
     * Use {@link PosixFileAttributeView#setPermissions(Set)} if possible, otherwise use
1166
     * {@link #setFilePermissionsFromPOSIX(File, String, GroupAndOthers)}.
1177
     * {@link #setFilePermissionsFromPOSIX(File, String, GroupAndOthers)}.
1167
     * 
1178
     * 
1168
     * @param p the path to change.
1179
     * @param p the path to change.
1169
     * @param posixPerms the new permissions to apply.
1180
     * @param posixPerms the new permissions to apply.
1170
     * @param groupAndOthers only for non-POSIX FS, how to merge group and others portion.
1181
     * @param groupAndOthers only for non-POSIX FS, how to merge group and others portion.
1171
     * @return the permission applied, 9 characters for POSIX, 6 for non-POSIX (i.e. 3 for owner, 3
1182
     * @return the permission applied, 9 characters for POSIX, 6 for non-POSIX (i.e. 3 for owner, 3
1172
     *         for the rest), <code>null</code> if some permissions couldn't be applied (only on
1183
     *         for the rest), <code>null</code> if some permissions couldn't be applied (only on
1173
     *         non-POSIX).
1184
     *         non-POSIX).
1174
     * @throws IOException if permissions couldn't be applied.
1185
     * @throws IOException if permissions couldn't be applied.
1175
     */
1186
     */
1176
    public static final String setPermissions(final Path p, final String posixPerms, final GroupAndOthers groupAndOthers) throws IOException {
1187
    public static final String setPermissions(final Path p, final String posixPerms, final GroupAndOthers groupAndOthers) throws IOException {
1177
        final String res;
1188
        final String res;
1178
        final PosixFileAttributeView view = Files.getFileAttributeView(p, PosixFileAttributeView.class);
1189
        final PosixFileAttributeView view = Files.getFileAttributeView(p, PosixFileAttributeView.class);
1179
        if (view != null) {
1190
        if (view != null) {
1180
            view.setPermissions(PosixFilePermissions.fromString(posixPerms));
1191
            view.setPermissions(PosixFilePermissions.fromString(posixPerms));
1181
            res = posixPerms;
1192
            res = posixPerms;
1182
        } else {
1193
        } else {
1183
            // final Set<Permission> notOwnerPerms = setFilePermissions(p.toFile(), pfp,
1194
            // final Set<Permission> notOwnerPerms = setFilePermissions(p.toFile(), pfp,
1184
            // groupAndOthers);
1195
            // groupAndOthers);
1185
            final Set<Permission> notOwnerPerms = setFilePermissionsFromPOSIX(p.toFile(), posixPerms, groupAndOthers);
1196
            final Set<Permission> notOwnerPerms = setFilePermissionsFromPOSIX(p.toFile(), posixPerms, groupAndOthers);
1186
            res = notOwnerPerms == null ? null : posixPerms.substring(0, 3) + Permission.get3chars(notOwnerPerms);
1197
            res = notOwnerPerms == null ? null : posixPerms.substring(0, 3) + Permission.get3chars(notOwnerPerms);
1187
        }
1198
        }
1188
        return res;
1199
        return res;
1189
    }
1200
    }
1190
 
1201
 
1191
    public static final Set<Permission> setFilePermissionsFromPOSIX(final File f, final String posixPerms) {
1202
    public static final Set<Permission> setFilePermissionsFromPOSIX(final File f, final String posixPerms) {
1192
        return setFilePermissionsFromPOSIX(f, posixPerms, GroupAndOthers.RESTRICTIVE);
1203
        return setFilePermissionsFromPOSIX(f, posixPerms, GroupAndOthers.RESTRICTIVE);
1193
    }
1204
    }
1194
 
1205
 
1195
    /**
1206
    /**
1196
     * This method doesn't need POSIX but must merge permissions before applying them.
1207
     * This method doesn't need POSIX but must merge permissions before applying them.
1197
     * 
1208
     * 
1198
     * @param f the file to change.
1209
     * @param f the file to change.
1199
     * @param posixPerms the POSIX permissions to merge.
1210
     * @param posixPerms the POSIX permissions to merge.
1200
     * @param groupAndOthers how to merge.
1211
     * @param groupAndOthers how to merge.
1201
     * @return the merged permissions for the "not owner" portion, or <code>null</code> if some
1212
     * @return the merged permissions for the "not owner" portion, or <code>null</code> if some
1202
     *         permissions couldn't be set.
1213
     *         permissions couldn't be set.
1203
     * @see #setFilePermissions(File, Set, Set)
1214
     * @see #setFilePermissions(File, Set, Set)
1204
     */
1215
     */
1205
    public static final Set<Permission> setFilePermissionsFromPOSIX(final File f, final String posixPerms, final GroupAndOthers groupAndOthers) {
1216
    public static final Set<Permission> setFilePermissionsFromPOSIX(final File f, final String posixPerms, final GroupAndOthers groupAndOthers) {
1206
        if (posixPerms.length() != 9)
1217
        if (posixPerms.length() != 9)
1207
            throw new IllegalArgumentException("Invalid mode : " + posixPerms);
1218
            throw new IllegalArgumentException("Invalid mode : " + posixPerms);
1208
        final Set<Permission> ownerPerms = Permission.fromString(posixPerms.substring(0, 3));
1219
        final Set<Permission> ownerPerms = Permission.fromString(posixPerms.substring(0, 3));
1209
        final Set<Permission> notOwnerPerms = groupAndOthers.getPermissions(posixPerms);
1220
        final Set<Permission> notOwnerPerms = groupAndOthers.getPermissions(posixPerms);
1210
        assert notOwnerPerms != null;
1221
        assert notOwnerPerms != null;
1211
        final boolean success = setFilePermissions(f, ownerPerms, notOwnerPerms);
1222
        final boolean success = setFilePermissions(f, ownerPerms, notOwnerPerms);
1212
        return success ? notOwnerPerms : null;
1223
        return success ? notOwnerPerms : null;
1213
    }
1224
    }
1214
 
1225
 
1215
    /**
1226
    /**
1216
     * Use {@link File} methods to set permissions. This works everywhere but group and others are
1227
     * Use {@link File} methods to set permissions. This works everywhere but group and others are
1217
     * treated as the same.
1228
     * treated as the same.
1218
     * 
1229
     * 
1219
     * @param f the file to change.
1230
     * @param f the file to change.
1220
     * @param owner the permissions for the owner.
1231
     * @param owner the permissions for the owner.
1221
     * @param notOwner the permissions for not the owner.
1232
     * @param notOwner the permissions for not the owner.
1222
     * @return <code>true</code> if all asked permissions were set.
1233
     * @return <code>true</code> if all asked permissions were set.
1223
     * @see File#setReadable(boolean, boolean)
1234
     * @see File#setReadable(boolean, boolean)
1224
     * @see File#setWritable(boolean, boolean)
1235
     * @see File#setWritable(boolean, boolean)
1225
     * @see File#setExecutable(boolean, boolean)
1236
     * @see File#setExecutable(boolean, boolean)
1226
     */
1237
     */
1227
    public static final boolean setFilePermissions(final File f, final Set<Permission> owner, final Set<Permission> notOwner) {
1238
    public static final boolean setFilePermissions(final File f, final Set<Permission> owner, final Set<Permission> notOwner) {
1228
        boolean res = setFilePermissions(f, notOwner, false);
1239
        boolean res = setFilePermissions(f, notOwner, false);
1229
        if (!owner.equals(notOwner)) {
1240
        if (!owner.equals(notOwner)) {
1230
            res &= setFilePermissions(f, owner, true);
1241
            res &= setFilePermissions(f, owner, true);
1231
        }
1242
        }
1232
        return res;
1243
        return res;
1233
    }
1244
    }
1234
 
1245
 
1235
    public static final boolean setFilePermissions(final File f, final Set<Permission> perms, final boolean ownerOnly) {
1246
    public static final boolean setFilePermissions(final File f, final Set<Permission> perms, final boolean ownerOnly) {
1236
        boolean res = f.setReadable(perms.contains(Permission.READ), ownerOnly);
1247
        boolean res = f.setReadable(perms.contains(Permission.READ), ownerOnly);
1237
        res &= f.setWritable(perms.contains(Permission.WRITE), ownerOnly);
1248
        res &= f.setWritable(perms.contains(Permission.WRITE), ownerOnly);
1238
        res &= f.setExecutable(perms.contains(Permission.EXECUTE), ownerOnly);
1249
        res &= f.setExecutable(perms.contains(Permission.EXECUTE), ownerOnly);
1239
        return res;
1250
        return res;
1240
    }
1251
    }
1241
 
1252
 
1242
    public static enum Permission {
1253
    public static enum Permission {
1243
        READ, WRITE, EXECUTE;
1254
        READ, WRITE, EXECUTE;
1244
 
1255
 
1245
        public static final Permission R = READ;
1256
        public static final Permission R = READ;
1246
        public static final Permission W = WRITE;
1257
        public static final Permission W = WRITE;
1247
        public static final Permission X = EXECUTE;
1258
        public static final Permission X = EXECUTE;
1248
        public static final Map<String, Set<Permission>> FROM_STRING = new HashMap<>();
1259
        public static final Map<String, Set<Permission>> FROM_STRING = new HashMap<>();
1249
        public static final Pattern MINUS_PATTERN = Pattern.compile("-+");
1260
        public static final Pattern MINUS_PATTERN = Pattern.compile("-+");
1250
        static {
1261
        static {
1251
            putString("---", Collections.<Permission> emptySet());
1262
            putString("---", Collections.<Permission> emptySet());
1252
            putString("--x", Collections.singleton(EXECUTE));
1263
            putString("--x", Collections.singleton(EXECUTE));
1253
            putString("-w-", Collections.singleton(WRITE));
1264
            putString("-w-", Collections.singleton(WRITE));
1254
            putString("-wx", EnumSet.of(WRITE, EXECUTE));
1265
            putString("-wx", EnumSet.of(WRITE, EXECUTE));
1255
            putString("r--", Collections.singleton(READ));
1266
            putString("r--", Collections.singleton(READ));
1256
            putString("r-x", EnumSet.of(READ, EXECUTE));
1267
            putString("r-x", EnumSet.of(READ, EXECUTE));
1257
            putString("rw-", EnumSet.of(READ, WRITE));
1268
            putString("rw-", EnumSet.of(READ, WRITE));
1258
            putString("rwx", EnumSet.allOf(Permission.class));
1269
            putString("rwx", EnumSet.allOf(Permission.class));
1259
        }
1270
        }
1260
 
1271
 
1261
        static private final void putString(final String str, final EnumSet<Permission> set) {
1272
        static private final void putString(final String str, final EnumSet<Permission> set) {
1262
            putString(str, Collections.unmodifiableSet(set));
1273
            putString(str, Collections.unmodifiableSet(set));
1263
        }
1274
        }
1264
 
1275
 
1265
        static private final void putString(final String str, final Set<Permission> unmodifiableSet) {
1276
        static private final void putString(final String str, final Set<Permission> unmodifiableSet) {
1266
            FROM_STRING.put(str, unmodifiableSet);
1277
            FROM_STRING.put(str, unmodifiableSet);
1267
            FROM_STRING.put(MINUS_PATTERN.matcher(str).replaceAll(""), unmodifiableSet);
1278
            FROM_STRING.put(MINUS_PATTERN.matcher(str).replaceAll(""), unmodifiableSet);
1268
        }
1279
        }
1269
 
1280
 
1270
        static public final Set<Permission> fromString(final String str) {
1281
        static public final Set<Permission> fromString(final String str) {
1271
            final Set<Permission> res = FROM_STRING.get(str);
1282
            final Set<Permission> res = FROM_STRING.get(str);
1272
            if (res == null)
1283
            if (res == null)
1273
                throw new IllegalArgumentException("Invalid string : " + str);
1284
                throw new IllegalArgumentException("Invalid string : " + str);
1274
            return res;
1285
            return res;
1275
        }
1286
        }
1276
 
1287
 
1277
        public static final String get3chars(final Set<Permission> perms) {
1288
        public static final String get3chars(final Set<Permission> perms) {
1278
            return get3chars(perms.contains(READ), perms.contains(WRITE), perms.contains(EXECUTE));
1289
            return get3chars(perms.contains(READ), perms.contains(WRITE), perms.contains(EXECUTE));
1279
        }
1290
        }
1280
 
1291
 
1281
        private static final String get3chars(final boolean read, final boolean write, final boolean exec) {
1292
        private static final String get3chars(final boolean read, final boolean write, final boolean exec) {
1282
            final StringBuilder sb = new StringBuilder(3);
1293
            final StringBuilder sb = new StringBuilder(3);
1283
            sb.append(read ? 'r' : '-');
1294
            sb.append(read ? 'r' : '-');
1284
            sb.append(write ? 'w' : '-');
1295
            sb.append(write ? 'w' : '-');
1285
            sb.append(exec ? 'x' : '-');
1296
            sb.append(exec ? 'x' : '-');
1286
            return sb.toString();
1297
            return sb.toString();
1287
        }
1298
        }
1288
    }
1299
    }
1289
}
1300
}