OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 17 | Rev 57 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
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
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.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
16
import org.openconcerto.utils.StringUtils.Escaper;
17
import org.openconcerto.utils.cc.ExnTransformer;
18
import org.openconcerto.utils.cc.IClosure;
19
 
20
import java.awt.Desktop;
21
import java.io.BufferedReader;
22
import java.io.BufferedWriter;
23
import java.io.File;
24
import java.io.FileFilter;
25
import java.io.FileInputStream;
26
import java.io.FileOutputStream;
27
import java.io.IOException;
28
import java.io.InputStream;
29
import java.io.InputStreamReader;
30
import java.io.OutputStreamWriter;
31
import java.io.RandomAccessFile;
32
import java.io.Reader;
33
import java.net.URL;
34
import java.nio.channels.FileChannel;
35
import java.util.ArrayList;
36
import java.util.Arrays;
37
import java.util.Collection;
38
import java.util.Collections;
39
import java.util.HashMap;
40
import java.util.List;
41
import java.util.Map;
42
import java.util.Set;
43
 
44
public final class FileUtils {
45
 
46
    private FileUtils() {
47
        // all static
48
    }
49
 
50
    public static void browseFile(File f) {
51
        if (Desktop.isDesktopSupported()) {
52
            Desktop d = Desktop.getDesktop();
53
            if (d.isSupported(Desktop.Action.BROWSE)) {
54
 
55
                try {
56
                    d.browse(f.getCanonicalFile().toURI());
57
                } catch (IOException e) {
58
                    // TODO Auto-generated catch block
59
                    e.printStackTrace();
60
                }
61
            } else {
62
                try {
63
                    openNative(f);
64
                } catch (IOException e) {
65
                    // TODO Auto-generated catch block
66
                    e.printStackTrace();
67
                }
68
            }
69
        } else {
70
            try {
71
                openNative(f);
72
            } catch (IOException e) {
73
                // TODO Auto-generated catch block
74
                e.printStackTrace();
75
            }
76
        }
77
    }
78
 
79
    public static void openFile(File f) throws IOException {
80
        if (Desktop.isDesktopSupported()) {
81
            Desktop d = Desktop.getDesktop();
82
            if (d.isSupported(Desktop.Action.OPEN)) {
83
                d.open(f.getCanonicalFile());
84
            } else {
85
                openNative(f);
86
            }
87
        } else {
88
            openNative(f);
89
        }
90
    }
91
 
92
    /**
93
     * All the files (see {@link File#isFile()}) contained in the passed dir.
94
     *
95
     * @param dir the root directory to search.
96
     * @return a List of String.
97
     */
98
    public static List<String> listR(File dir) {
99
        return listR_rec(dir, ".");
100
    }
101
 
102
    private static List<String> listR_rec(File dir, String prefix) {
103
        if (!dir.isDirectory())
104
            return null;
105
 
106
        final List<String> res = new ArrayList<String>();
107
        final File[] children = dir.listFiles();
108
        for (int i = 0; i < children.length; i++) {
109
            final String newPrefix = prefix + "/" + children[i].getName();
110
            if (children[i].isFile()) {
111
                // MAYBE add a way to restrict added files
112
                res.add(newPrefix);
113
            } else if (children[i].isDirectory()) {
114
                res.addAll(listR_rec(children[i], newPrefix));
115
            }
116
        }
117
        return res;
118
    }
119
 
120
    public static void walk(File dir, IClosure<File> c) {
121
        walk(dir, c, RecursionType.BREADTH_FIRST);
122
    }
123
 
124
    public static void walk(File dir, IClosure<File> c, RecursionType type) {
125
        if (type == RecursionType.BREADTH_FIRST)
126
            c.executeChecked(dir);
127
        if (dir.isDirectory()) {
128
            for (final File child : dir.listFiles()) {
129
                walk(child, c, type);
130
            }
131
        }
132
        if (type == RecursionType.DEPTH_FIRST)
133
            c.executeChecked(dir);
134
    }
135
 
136
    public static final List<File> list(File root, final int depth) {
137
        return list(root, depth, null);
138
    }
139
 
140
    /**
141
     * Finds all files at the specified depth below <code>root</code>.
142
     *
143
     * @param root the base directory
144
     * @param depth the depth of the returned files.
145
     * @param ff a filter, can be <code>null</code>.
146
     * @return a list of files <code>depth</code> levels beneath <code>root</code>.
147
     */
148
    public static final List<File> list(File root, final int depth, final FileFilter ff) {
149
        if (!root.exists())
150
            return Collections.<File> emptyList();
151
        if (depth == 0) {
152
            return ff.accept(root) ? Collections.singletonList(root) : Collections.<File> emptyList();
153
        } else if (depth == 1) {
154
            final File[] listFiles = root.listFiles(ff);
155
            if (listFiles == null)
156
                throw new IllegalStateException("cannot list " + root);
157
            return Arrays.asList(listFiles);
158
        } else {
159
            final File[] childDirs = root.listFiles(DIR_FILTER);
160
            if (childDirs == null)
161
                throw new IllegalStateException("cannot list " + root);
162
            final List<File> res = new ArrayList<File>();
163
            for (final File child : childDirs) {
164
                res.addAll(list(child, depth - 1, ff));
165
            }
166
            return res;
167
        }
168
    }
169
 
170
    /**
171
     * Returns the relative path from one file to another in the same filesystem tree. Files are not
172
     * required to exist, see {@link File#getCanonicalPath()}.
173
     *
174
     * @param fromDir the starting directory, eg /a/b/.
175
     * @param to the file to get to, eg /a/x/y.txt.
176
     * @return the relative path, eg "../x/y.txt".
177
     * @throws IOException if an error occurs while canonicalizing the files.
178
     * @throws IllegalArgumentException if fromDir exists and is not directory.
179
     */
180
    public static final String relative(File fromDir, File to) throws IOException {
181
        if (fromDir.exists() && !fromDir.isDirectory())
182
            throw new IllegalArgumentException(fromDir + " is not a directory");
183
 
184
        final File fromF = fromDir.getCanonicalFile();
185
        final File toF = to.getCanonicalFile();
186
        final List<File> toPath = getAncestors(toF);
187
        final List<File> fromPath = getAncestors(fromF);
188
 
189
        // no common ancestor (for example on Windows on 2 different letters)
190
        if (!toPath.get(0).equals(fromPath.get(0))) {
191
            // already canonical
192
            return toF.getPath();
193
        }
194
 
195
        int commonIndex = Math.min(toPath.size(), fromPath.size()) - 1;
196
        boolean found = false;
197
        while (commonIndex >= 0 && !found) {
198
            found = fromPath.get(commonIndex).equals(toPath.get(commonIndex));
199
            if (!found)
200
                commonIndex--;
201
        }
202
 
203
        // on remonte jusqu'à l'ancêtre commun
204
        final List<String> complete = new ArrayList<String>(Collections.nCopies(fromPath.size() - 1 - commonIndex, ".."));
205
        if (complete.isEmpty())
206
            complete.add(".");
207
        // puis on descend vers 'to'
208
        for (File f : toPath.subList(commonIndex + 1, toPath.size())) {
209
            complete.add(f.getName());
210
        }
211
 
212
        return CollectionUtils.join(complete, File.separator);
213
    }
214
 
215
    // return each ancestor of f (including itself)
216
    // eg [/, /folder, /folder/dir] for /folder/dir
217
    public final static List<File> getAncestors(File f) {
218
        final List<File> path = new ArrayList<File>();
219
        File currentF = f;
220
        while (currentF != null) {
221
            path.add(0, currentF);
222
            currentF = currentF.getParentFile();
223
        }
224
        return path;
225
    }
226
 
227
    public final static File addSuffix(File f, String suffix) {
228
        return new File(f.getParentFile(), f.getName() + suffix);
229
    }
230
 
231
    /**
232
     * Prepend a string to a suffix.
233
     *
234
     * @param f the file, e.g. "sample.xml".
235
     * @param toInsert the string to insert in the filename, e.g. "-sql".
236
     * @param suffix the suffix of <code>f</code>, e.g. ".xml".
237
     * @return a new file with <code>toInsert</code> prepended to <code>suffix</code>, e.g.
238
     *         "sample-sql.xml".
239
     */
240
    public final static File prependSuffix(File f, String toInsert, String suffix) {
241
        return new File(f.getParentFile(), removeSuffix(f.getName(), suffix) + toInsert + suffix);
242
    }
243
 
244
    public final static String removeSuffix(String name, String suffix) {
245
        return name.endsWith(suffix) ? name.substring(0, name.length() - suffix.length()) : name;
246
    }
247
 
248
    /**
249
     * Rename a file if necessary by finding a free name. The tested names are
250
     * <code>name + "_" + i + suffix</code>.
251
     *
252
     * @param parent the directory.
253
     * @param name the base name of the file.
254
     * @param suffix the suffix of the file, e.g. ".ods".
255
     * @return <code>new File(parent, name + suffix)</code> (always non existing) and the new file,
256
     *         (or <code>null</code> if no file was moved).
257
     */
258
    public final static File[] mvOut(final File parent, final String name, final String suffix) {
259
        final File fDest = new File(parent, name + suffix);
260
        final File renamed;
261
        if (fDest.exists()) {
262
            int i = 0;
263
            File free = fDest;
264
            while (free.exists()) {
265
                free = new File(parent, name + "_" + i + suffix);
266
                i++;
267
            }
268
            assert !fDest.equals(free);
269
            if (!fDest.renameTo(free))
270
                throw new IllegalStateException("Couldn't rename " + fDest + " to " + free);
271
            renamed = free;
272
        } else {
273
            renamed = null;
274
        }
275
        assert !fDest.exists();
276
        return new File[] { fDest, renamed };
277
    }
278
 
279
    // ** shell
280
 
281
    /**
282
     * Behave like the 'mv' unix utility, ie handle cross filesystems mv and <code>dest</code> being
283
     * a directory.
284
     *
285
     * @param f the source file.
286
     * @param dest the destination file or directory.
287
     * @return the error or <code>null</code> if there was none.
288
     */
289
    public static String mv(File f, File dest) {
290
        final File canonF;
291
        File canonDest;
292
        try {
293
            canonF = f.getCanonicalFile();
294
            canonDest = dest.getCanonicalFile();
295
        } catch (IOException e) {
296
            return ExceptionUtils.getStackTrace(e);
297
        }
298
        if (canonF.equals(canonDest))
299
            // nothing to do
300
            return null;
301
        if (canonDest.isDirectory())
302
            canonDest = new File(canonDest, canonF.getName());
303
 
304
        final File destF;
305
        if (canonDest.exists())
306
            return canonDest + " exists";
307
        else if (!canonDest.getParentFile().exists())
308
            return "parent of " + canonDest + " does not exist";
309
        else
310
            destF = canonDest;
311
        if (!canonF.renameTo(destF)) {
312
            try {
313
                copyDirectory(canonF, destF);
314
                if (destF.exists())
315
                    rmR(canonF);
316
            } catch (IOException e) {
317
                return ExceptionUtils.getStackTrace(e);
318
            }
319
        }
320
        return null;
321
    }
322
 
323
    // transferTo() can be limited by a number of factors, like the number of bits of the system
324
    // if mmap is used (e.g. on Linux) or by an arbitrary magic number on Windows : 64Mb - 32Kb
325
    private static final int CHANNEL_MAX_COUNT = Math.min(64 * 1024 * 1024 - 32 * 1024, Integer.MAX_VALUE);
326
 
327
    public static void copyFile(File in, File out) throws IOException {
328
        copyFile(in, out, CHANNEL_MAX_COUNT);
329
    }
330
 
331
    /**
332
     * Copy a file. It is generally not advised to use 0 for <code>maxCount</code> since various
333
     * implementations have size limitations, see {@link #copyFile(File, File)}.
334
     *
335
     * @param in the source file.
336
     * @param out the destination file.
337
     * @param maxCount the number of bytes to copy at a time, 0 meaning size of <code>in</code>.
338
     * @throws IOException if an error occurs.
339
     */
340
    public static void copyFile(File in, File out, long maxCount) throws IOException {
341
        final FileChannel sourceChannel = new FileInputStream(in).getChannel();
342
        final FileChannel destinationChannel = new FileOutputStream(out).getChannel();
343
        if (maxCount == 0)
344
            maxCount = sourceChannel.size();
345
        try {
346
            final long size = sourceChannel.size();
347
            long position = 0;
348
            while (position < size) {
349
                position += sourceChannel.transferTo(position, maxCount, destinationChannel);
350
            }
351
        } finally {
352
            sourceChannel.close();
353
            destinationChannel.close();
354
        }
355
    }
356
 
25 ilm 357
    public static void copyFile(File in, File out, final boolean useTime) throws IOException {
358
        if (!useTime || in.lastModified() != out.lastModified()) {
359
            copyFile(in, out);
360
            if (useTime)
361
                out.setLastModified(in.lastModified());
362
        }
363
    }
364
 
17 ilm 365
    public static void copyDirectory(File in, File out) throws IOException {
366
        copyDirectory(in, out, Collections.<String> emptySet());
367
    }
368
 
369
    public static final Set<String> VersionControl = CollectionUtils.createSet(".svn", "CVS");
370
 
371
    public static void copyDirectory(File in, File out, final Set<String> toIgnore) throws IOException {
25 ilm 372
        copyDirectory(in, out, toIgnore, false);
373
    }
374
 
375
    public static void copyDirectory(File in, File out, final Set<String> toIgnore, final boolean useTime) throws IOException {
17 ilm 376
        if (toIgnore.contains(in.getName()))
377
            return;
378
 
379
        if (in.isDirectory()) {
380
            if (!out.exists()) {
381
                out.mkdir();
382
            }
383
 
384
            String[] children = in.list();
385
            for (int i = 0; i < children.length; i++) {
25 ilm 386
                copyDirectory(new File(in, children[i]), new File(out, children[i]), toIgnore, useTime);
17 ilm 387
            }
388
        } else {
389
            if (!in.getName().equals("Thumbs.db")) {
25 ilm 390
                copyFile(in, out, useTime);
17 ilm 391
            }
392
        }
393
    }
394
 
395
    /**
396
     * Delete recursively the passed directory. If a deletion fails, the method stops attempting to
397
     * delete and returns false.
398
     *
399
     * @param dir the dir to be deleted.
400
     * @return <code>true</code> if all deletions were successful.
401
     */
402
    public static boolean rmR(File dir) {
403
        if (dir.isDirectory()) {
404
            File[] children = dir.listFiles();
405
            for (int i = 0; i < children.length; i++) {
406
                boolean success = rmR(children[i]);
407
 
408
                if (!success) {
409
 
410
                    return false;
411
                }
412
            }
413
        }
414
 
415
        // The directory is now empty so delete it
416
        return dir.delete();
417
    }
418
 
25 ilm 419
    public static void rm_R(File dir) throws IOException {
420
        if (dir.isDirectory()) {
421
            for (final File child : dir.listFiles()) {
422
                rmR(child);
423
            }
424
        }
425
        // The directory is now empty so delete it
426
        rm(dir);
427
    }
428
 
429
    public static void rm(File f) throws IOException {
430
        if (f.exists() && !f.delete())
431
            throw new IOException("cannot delete " + f);
432
    }
433
 
17 ilm 434
    public static final File mkdir_p(File dir) throws IOException {
435
        if (!dir.exists()) {
436
            if (!dir.mkdirs()) {
437
                throw new IOException("cannot create directory " + dir);
438
            }
439
        }
440
        return dir;
441
    }
442
 
443
    /**
444
     * Create all ancestors of <code>f</code>.
445
     *
446
     * @param f any file whose ancestors should be created.
447
     * @return <code>f</code>.
448
     * @throws IOException if ancestors cannot be created.
449
     */
450
    public static final File mkParentDirs(File f) throws IOException {
451
        final File parentFile = f.getParentFile();
452
        if (parentFile != null)
453
            mkdir_p(parentFile);
454
        return f;
455
    }
456
 
457
    // **io
458
 
459
    /**
460
     * Read a file line by line with the default encoding and returns the concatenation of these.
461
     *
462
     * @param f the file to read.
463
     * @return the content of f.
464
     * @throws IOException if a pb occur while reading.
465
     */
466
    public static final String read(File f) throws IOException {
467
        return read(f, null);
468
    }
469
 
470
    /**
471
     * Read a file line by line and returns the concatenation of these.
472
     *
473
     * @param f the file to read.
474
     * @param charset the encoding of <code>f</code>, <code>null</code> means default encoding.
475
     * @return the content of f.
476
     * @throws IOException if a pb occur while reading.
477
     */
478
    public static final String read(File f, String charset) throws IOException {
479
        return read(new FileInputStream(f), charset);
480
    }
481
 
482
    public static final String read(InputStream ins, String charset) throws IOException {
483
        final Reader reader;
484
        if (charset == null)
485
            reader = new InputStreamReader(ins);
486
        else
487
            reader = new InputStreamReader(ins, charset);
488
        return read(reader);
489
    }
490
 
491
    public static final String read(final Reader reader) throws IOException {
492
        return read(reader, 8192);
493
    }
494
 
495
    public static final String read(final Reader reader, final int bufferSize) throws IOException {
496
        final StringBuilder sb = new StringBuilder();
497
        final char[] buffer = new char[bufferSize];
498
        final BufferedReader in = new BufferedReader(reader);
499
        try {
500
            while (true) {
501
                final int count = in.read(buffer);
502
                if (count == -1)
503
                    break;
504
                sb.append(buffer, 0, count);
505
            }
506
        } finally {
507
            in.close();
508
        }
509
        return sb.toString();
510
    }
511
 
512
    /**
513
     * Read the whole content of a file.
514
     *
515
     * @param f the file to read.
516
     * @return its content.
517
     * @throws IOException if a pb occur while reading.
518
     * @throws IllegalArgumentException if f is longer than <code>Integer.MAX_VALUE</code>.
519
     */
520
    public static final byte[] readBytes(File f) throws IOException {
521
        // no need for a Buffer since we read everything at once
522
        final InputStream in = new FileInputStream(f);
523
        if (f.length() > Integer.MAX_VALUE)
524
            throw new IllegalArgumentException("file longer than Integer.MAX_VALUE" + f.length());
525
        final byte[] res = new byte[(int) f.length()];
526
        in.read(res);
527
        in.close();
528
        return res;
529
    }
530
 
531
    public static void write(String s, File f) throws IOException {
532
        write(s, f, null, false);
533
    }
534
 
535
    public static void write(String s, File f, String charset, boolean append) throws IOException {
536
        final FileOutputStream fileStream = new FileOutputStream(f, append);
537
        final OutputStreamWriter out = charset == null ? new OutputStreamWriter(fileStream) : new OutputStreamWriter(fileStream, charset);
538
        final BufferedWriter w = new BufferedWriter(out);
539
        try {
540
            w.write(s);
541
        } finally {
542
            w.close();
543
        }
544
    }
545
 
546
    /**
547
     * Execute the passed transformer with the lock on the passed file.
548
     *
549
     * @param <T> return type.
550
     * @param f the file to lock.
551
     * @param transf what to do on the file.
552
     * @return what <code>transf</code> returns.
553
     * @throws Exception if an error occurs.
554
     */
555
    public static final <T> T doWithLock(final File f, ExnTransformer<RandomAccessFile, T, ?> transf) throws Exception {
556
        // don't use FileOutputStream : it truncates the file on creation
557
        RandomAccessFile out = null;
558
        try {
559
            mkParentDirs(f);
560
            // we need write to obtain lock
561
            out = new RandomAccessFile(f, "rw");
562
            out.getChannel().lock();
563
            final T res = transf.transformChecked(out);
564
            // this also release the lock
565
            out.close();
566
            out = null;
567
            return res;
568
        } catch (final Exception e) {
569
            // if anything happens, try to close
570
            // don't use finally{close()} otherwise if it raise an exception
571
            // the original error is discarded
572
            Exception toThrow = e;
573
            if (out != null)
574
                try {
575
                    out.close();
576
                } catch (final IOException e2) {
577
                    // too bad, just add the error
578
                    toThrow = ExceptionUtils.createExn(IOException.class, "couldn't close: " + e2.getMessage(), e);
579
                }
580
            throw toThrow;
581
        }
582
    }
583
 
584
    private static final Map<URL, File> files = new HashMap<URL, File>();
585
 
586
    private static final File getShortCutFile() throws IOException {
587
        return getFile(FileUtils.class.getResource("shortcut.vbs"));
588
    }
589
 
590
    // windows cannot execute a string, it demands a file
591
    public static final File getFile(final URL url) throws IOException {
592
        final File shortcutFile;
593
        final File currentFile = files.get(url);
594
        if (currentFile == null || !currentFile.exists()) {
595
            shortcutFile = File.createTempFile("windowsIsLame", ".vbs");
596
            shortcutFile.deleteOnExit();
597
            files.put(url, shortcutFile);
598
            final InputStream stream = url.openStream();
599
            final FileOutputStream out = new FileOutputStream(shortcutFile);
600
            try {
601
                StreamUtils.copy(stream, out);
602
            } finally {
603
                out.close();
604
                stream.close();
605
            }
606
        } else
607
            shortcutFile = currentFile;
608
        return shortcutFile;
609
    }
610
 
611
    /**
612
     * Create a symbolic link from <code>link</code> to <code>target</code>.
613
     *
614
     * @param target the target of the link, eg ".".
615
     * @param link the file to create or replace, eg "l".
616
     * @return the link if the creation was successfull, <code>null</code> otherwise, eg "l.LNK".
617
     * @throws IOException if an error occurs.
618
     */
619
    public static final File ln(final File target, final File link) throws IOException {
620
        final String os = System.getProperty("os.name");
621
        final Process ps;
622
        final File res;
623
        if (os.startsWith("Windows")) {
624
            // using the .vbs since it doesn't depends on cygwin
625
            // and cygwin's ln is weird :
626
            // 1. needs CYGWIN=winsymlinks to create a shortcut, but even then "ln -f" doesn't work
627
            // since it tries to delete l instead of l.LNK
628
            // 2. it sets the system flag so "dir" doesn't show the shortcut (unless you add /AS)
629
            // 3. the shortcut is recognized as a symlink thanks to a special attribute that can get
630
            // lost (e.g. copying in eclipse)
631
            ps = Runtime.getRuntime().exec(new String[] { "cscript", getShortCutFile().getAbsolutePath(), link.getAbsolutePath(), target.getCanonicalPath() });
632
            res = new File(link.getParentFile(), link.getName() + ".LNK");
633
        } else {
634
            final String rel = FileUtils.relative(link.getAbsoluteFile().getParentFile(), target);
635
            // add -f to replace existing links
636
            // add -n so that ln -sf aDir anExistantLinkToIt succeed
637
            final String[] cmdarray = { "ln", "-sfn", rel, link.getAbsolutePath() };
638
            ps = Runtime.getRuntime().exec(cmdarray);
639
            res = link;
640
        }
641
        try {
642
            final int exitValue = ps.waitFor();
643
            if (exitValue == 0)
644
                return res;
645
            else
646
                throw new IOException("Abnormal exit value: " + exitValue);
647
        } catch (InterruptedException e) {
648
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
649
        }
650
    }
651
 
652
    /**
653
     * Resolve a symbolic link or a windows shortcut.
654
     *
655
     * @param link the shortcut, e.g. shortcut.lnk.
656
     * @return the target of <code>link</code>, <code>null</code> if not found, e.g. target.txt.
657
     * @throws IOException if an error occurs.
658
     */
659
    public static final File readlink(final File link) throws IOException {
660
        final String os = System.getProperty("os.name");
661
        final Process ps;
662
        if (os.startsWith("Windows")) {
663
            ps = Runtime.getRuntime().exec(new String[] { "cscript", "//NoLogo", getShortCutFile().getAbsolutePath(), link.getAbsolutePath() });
664
        } else {
665
            // add -f to canonicalize
666
            ps = Runtime.getRuntime().exec(new String[] { "readlink", "-f", link.getAbsolutePath() });
667
        }
668
        try {
669
            final BufferedReader reader = new BufferedReader(new InputStreamReader(ps.getInputStream()));
670
            final String res = reader.readLine();
671
            reader.close();
672
            if (ps.waitFor() != 0 || res == null || res.length() == 0)
673
                return null;
674
            else
675
                return new File(res);
676
        } catch (InterruptedException e) {
677
            throw ExceptionUtils.createExn(IOException.class, "interrupted", e);
678
        }
679
    }
680
 
681
    /**
682
     * Tries to open the passed file as if it were graphically opened by the current user (respect
683
     * user's "open with"). If a native way to open the file can't be found, tries the passed list
684
     * of executables.
685
     *
686
     * @param f the file to open.
687
     * @param executables a list of executables to try, e.g. ["ooffice", "soffice"].
688
     * @throws IOException if the file can't be opened.
689
     */
690
    public static final void open(File f, String[] executables) throws IOException {
691
        try {
692
            openNative(f);
693
        } catch (IOException exn) {
694
            for (int i = 0; i < executables.length; i++) {
695
                final String executable = executables[i];
696
                try {
697
                    Runtime.getRuntime().exec(new String[] { executable, f.getCanonicalPath() });
698
                    return;
699
                } catch (IOException e) {
700
                    // try the next one
701
                }
702
            }
703
            throw ExceptionUtils.createExn(IOException.class, "unable to open " + f + " with: " + Arrays.asList(executables), exn);
704
        }
705
    }
706
 
707
    /**
708
     * Open the passed file as if it were graphically opened by the current user (user's "open
709
     * with").
710
     *
711
     * @param f the file to open.
712
     * @throws IOException if f couldn't be opened.
713
     */
714
    private static final void openNative(File f) throws IOException {
715
        final String os = System.getProperty("os.name");
716
        final String[] cmdarray;
717
        if (os.startsWith("Windows")) {
718
            cmdarray = new String[] { "cmd", "/c", "start", "\"\"", f.getCanonicalPath() };
719
        } else if (os.startsWith("Mac OS")) {
720
            cmdarray = new String[] { "open", f.getCanonicalPath() };
721
        } else if (os.startsWith("Linux")) {
722
            cmdarray = new String[] { "xdg-open", f.getCanonicalPath() };
723
        } else {
724
            throw new IOException("unknown way to open " + f);
725
        }
726
        try {
727
            // can wait since the command return as soon as the native application is launched
728
            // (i.e. this won't wait 30s for OpenOffice)
729
            final int res = Runtime.getRuntime().exec(cmdarray).waitFor();
730
            if (res != 0)
731
                throw new IOException("error (" + res + ") executing " + Arrays.asList(cmdarray));
732
        } catch (InterruptedException e) {
733
            throw ExceptionUtils.createExn(IOException.class, "interrupted waiting for " + Arrays.asList(cmdarray), e);
734
        }
735
    }
736
 
737
    static final boolean gnomeRunning() {
738
        try {
739
            return Runtime.getRuntime().exec(new String[] { "pgrep", "-u", System.getProperty("user.name"), "nautilus" }).waitFor() == 0;
740
        } catch (Exception e) {
741
            return false;
742
        }
743
    }
744
 
745
    private static final Map<String, String> ext2mime;
746
    static {
747
        ext2mime = new HashMap<String, String>();
748
        ext2mime.put(".xml", "text/xml");
749
        ext2mime.put(".jpg", "image/jpeg");
750
        ext2mime.put(".png", "image/png");
751
        ext2mime.put(".tiff", "image/tiff");
752
    }
753
 
754
    /**
755
     * Try to guess the media type of the passed file name (see <a
756
     * href="http://www.iana.org/assignments/media-types">iana</a>).
757
     *
758
     * @param fname a file name.
759
     * @return its mime type.
760
     */
761
    public static final String findMimeType(String fname) {
762
        for (final Map.Entry<String, String> e : ext2mime.entrySet()) {
763
            if (fname.toLowerCase().endsWith(e.getKey()))
764
                return e.getValue();
765
        }
766
        return null;
767
    }
768
 
769
    /**
770
     * Chars not valid in filenames.
771
     */
772
    public static final Collection<Character> INVALID_CHARS;
773
 
774
    /**
775
     * An escaper suitable for producing valid filenames.
776
     */
777
    public static final Escaper FILENAME_ESCAPER = new StringUtils.Escaper('\'', 'Q');
778
    static {
779
        // from windows explorer
780
        FILENAME_ESCAPER.add('"', 'D').add(':', 'C').add('/', 'S').add('\\', 'A');
781
        FILENAME_ESCAPER.add('<', 'L').add('>', 'G').add('*', 'R').add('|', 'P').add('?', 'M');
782
        INVALID_CHARS = FILENAME_ESCAPER.getEscapedChars();
783
    }
784
 
785
    public static final FileFilter DIR_FILTER = new FileFilter() {
786
        @Override
787
        public boolean accept(File f) {
788
            return f.isDirectory();
789
        }
790
    };
791
    public static final FileFilter REGULAR_FILE_FILTER = new FileFilter() {
792
        @Override
793
        public boolean accept(File f) {
794
            return f.isFile();
795
        }
796
    };
797
 
798
    /**
799
     * Return a filter that select regular files ending in <code>ext</code>.
800
     *
801
     * @param ext the end of the name, eg ".xml".
802
     * @return the corresponding filter.
803
     */
804
    public static final FileFilter createEndFileFilter(final String ext) {
805
        return new FileFilter() {
806
            @Override
807
            public boolean accept(File f) {
808
                return f.isFile() && f.getName().endsWith(ext);
809
            }
810
        };
811
    }
812
}