001package jmri.util;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.IOException;
006import java.io.InputStream;
007import java.net.URI;
008import java.net.URL;
009import java.util.Map;
010import java.util.Set;
011import java.util.jar.JarFile;
012import javax.annotation.CheckReturnValue;
013import javax.annotation.Nonnull;
014import javax.annotation.CheckForNull;
015
016import jmri.profile.Profile;
017import jmri.profile.ProfileManager;
018
019/**
020 * Common utility methods for working with Files.
021 * <p>
022 * All methods in this class call the identical method from the default instance
023 * of {@link FileUtilSupport}.
024 *
025 * @author Bob Jacobsen Copyright 2003, 2005, 2006
026 * @author Randall Wood Copyright 2012, 2013, 2014, 2016, 2019
027 * @see FileUtilSupport
028 */
029public final class FileUtil {
030
031    /**
032     * Portable reference to items in the JMRI program directory.
033     */
034    static public final String PROGRAM = "program:"; // NOI18N
035    /**
036     * Portable reference to the JMRI user's files and preferences directory.
037     */
038    static public final String PREFERENCES = "preference:"; // NOI18N
039    /**
040     * Portable reference to the JMRI applications preferences directory.
041     */
042    static public final String SETTINGS = "settings:"; // NOI18N
043    /**
044     * Portable reference to the user's home directory.
045     */
046    static public final String HOME = "home:"; // NOI18N
047    /**
048     * Portable reference to the current profile directory.
049     */
050    static public final String PROFILE = "profile:"; // NOI18N
051    /**
052     * Portable reference to the current scripts directory.
053     */
054    static public final String SCRIPTS = "scripts:"; // NOI18N
055    /**
056     * The portable file path component separator.
057     */
058    static public final char SEPARATOR = '/'; // NOI18N
059
060    /**
061     * The types of locations to use when falling back on default locations in
062     * {@link #findURI(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
063     */
064    static public enum Location {
065        INSTALLED, USER, ALL, NONE
066    }
067
068    /**
069     * Get the {@link java.io.File} that path refers to. Throws a
070     * {@link java.io.FileNotFoundException} if the file cannot be found instead
071     * of returning null (as File would). Use {@link #getURI(java.lang.String) }
072     * or {@link #getURL(java.lang.String) } instead of this method if possible.
073     *
074     * @param path the path to find
075     * @return {@link java.io.File} at path
076     * @throws java.io.FileNotFoundException if path cannot be found
077     * @see #getURI(java.lang.String)
078     * @see #getURL(java.lang.String)
079     */
080    @Nonnull
081    @CheckReturnValue
082    static public File getFile(@Nonnull String path) throws FileNotFoundException {
083        return FileUtilSupport.getDefault().getFile(path);
084    }
085
086    /**
087     * Get the {@link java.io.File} that path refers to. Throws a
088     * {@link java.io.FileNotFoundException} if the file cannot be found instead
089     * of returning null (as File would). Use {@link #getURI(java.lang.String) }
090     * or {@link #getURL(java.lang.String) } instead of this method if possible.
091     *
092     * @param profile the profile to use as a base
093     * @param path    the path to find
094     * @return {@link java.io.File} at path
095     * @throws java.io.FileNotFoundException if path cannot be found
096     * @see #getURI(java.lang.String)
097     * @see #getURL(java.lang.String)
098     */
099    @Nonnull
100    @CheckReturnValue
101    static public File getFile(@CheckForNull Profile profile, @Nonnull String path) throws FileNotFoundException {
102        return FileUtilSupport.getDefault().getFile(profile, path);
103    }
104
105    /**
106     * Get the {@link java.io.File} that path refers to. Throws a
107     * {@link java.io.FileNotFoundException} if the file cannot be found instead
108     * of returning null (as File would).
109     *
110     * @param path the path to find
111     * @return {@link java.io.File} at path
112     * @throws java.io.FileNotFoundException if path cannot be found
113     * @see #getFile(java.lang.String)
114     * @see #getURL(java.lang.String)
115     */
116    @Nonnull
117    @CheckReturnValue
118    static public URI getURI(@Nonnull String path) throws FileNotFoundException {
119        return FileUtilSupport.getDefault().getURI(path);
120    }
121
122    /**
123     * Get the {@link java.net.URL} that path refers to. Throws a
124     * {@link java.io.FileNotFoundException} if the URL cannot be found instead
125     * of returning null.
126     *
127     * @param path the path to find
128     * @return {@link java.net.URL} at path
129     * @throws java.io.FileNotFoundException if path cannot be found
130     * @see #getFile(java.lang.String)
131     * @see #getURI(java.lang.String)
132     */
133    @Nonnull
134    @CheckReturnValue
135    static public URL getURL(@Nonnull String path) throws FileNotFoundException {
136        return FileUtilSupport.getDefault().getURL(path);
137    }
138
139    /**
140     * Convenience method to get the {@link java.net.URL} from a
141     * {@link java.net.URI}. Logs errors and returns null if any exceptions are
142     * thrown by the conversion.
143     *
144     * @param uri The URI to convert.
145     * @return URL or null if any errors exist.
146     */
147    @CheckForNull
148    @CheckReturnValue
149    static public URL getURL(@Nonnull URI uri) {
150        return FileUtilSupport.getDefault().getURL(uri);
151    }
152
153    /**
154     * Find all files matching the given name under the given root directory
155     * within both the user and installed file locations.
156     *
157     * @param name the name of the file to find
158     * @param root the relative path to a directory in either or both of the
159     *             user or installed file locations; use a single period
160     *             character to refer to the root of the user or installed file
161     *             locations
162     * @return a set of found files or an empty set if no matching files were
163     *         found
164     * @throws IllegalArgumentException if the name is not a relative path, is
165     *                                  empty, or contains path separators; or
166     *                                  if the root is not a relative path, is
167     *                                  empty, or contains a parent directory
168     *                                  (..)
169     * @throws NullPointerException     if any parameter is null
170     */
171    @Nonnull
172    @CheckReturnValue
173    static public Set<File> findFiles(@Nonnull String name, @Nonnull String root) throws IllegalArgumentException {
174        return FileUtilSupport.getDefault().findFiles(name, root);
175    }
176
177    /**
178     * Find all files matching the given name under the given root directory
179     * within the specified location.
180     *
181     * @param name     the name of the file to find
182     * @param root     the relative path to a directory in either or both of the
183     *                 user or installed file locations; use a single period
184     *                 character to refer to the root of the location
185     * @param location the location to search within
186     * @return a set of found files or an empty set if no matching files were
187     *         found
188     * @throws IllegalArgumentException if the name is not a relative path, is
189     *                                  empty, or contains path separators; if
190     *                                  the root is not a relative path, is
191     *                                  empty, or contains a parent directory
192     *                                  (..); or if the location is
193     *                                  {@link Location#NONE}
194     * @throws NullPointerException     if any parameter is null
195     */
196    @Nonnull
197    @CheckReturnValue
198    static public Set<File> findFiles(@Nonnull String name, @Nonnull String root, @Nonnull Location location) {
199        return FileUtilSupport.getDefault().findFiles(name, root, location);
200    }
201
202    /**
203     * Get the resource file corresponding to a name. There are five cases:
204     * <ul>
205     * <li>Starts with "program:", treat the rest as a relative pathname below
206     * the program directory</li>
207     * <li>Starts with "preference:", treat the rest as a relative path below
208     * the user's files directory</li>
209     * <li>Starts with "settings:", treat the rest as a relative path below the
210     * JMRI system preferences directory</li>
211     * <li>Starts with "home:", treat the rest as a relative path below the
212     * user.home directory</li>
213     * <li>Starts with "profile:", treat the rest as a relative path below the
214     * profile directory as specified in the
215     * active{@link jmri.profile.Profile}</li>
216     * <li>Starts with "scripts:", treat the rest as a relative path below the
217     * scripts directory</li>
218     * <li>Otherwise, treat the name as a relative path below the program
219     * directory</li>
220     * </ul>
221     * In any case, absolute pathnames will work. Uses the Profile returned by
222     * {@link ProfileManager#getActiveProfile()} as the base.
223     *
224     * @param pName the name, possibly starting with home:, profile:, program:,
225     *              preference:, scripts:, or settings:
226     * @return Absolute file name to use, or null. This will include
227     *         system-specific file separators.
228     * @since 2.7.2
229     */
230    @Nonnull
231    @CheckReturnValue
232    static public String getExternalFilename(@Nonnull String pName) {
233        return FileUtilSupport.getDefault().getExternalFilename(pName);
234    }
235
236    /**
237     * Get the resource file corresponding to a name. There are five cases:
238     * <ul>
239     * <li>Starts with "program:", treat the rest as a relative pathname below
240     * the program directory</li>
241     * <li>Starts with "preference:", treat the rest as a relative path below
242     * the user's files directory</li>
243     * <li>Starts with "settings:", treat the rest as a relative path below the
244     * JMRI system preferences directory</li>
245     * <li>Starts with "home:", treat the rest as a relative path below the
246     * user.home directory</li>
247     * <li>Starts with "profile:", treat the rest as a relative path below the
248     * profile directory as specified in the
249     * active{@link jmri.profile.Profile}</li>
250     * <li>Starts with "scripts:", treat the rest as a relative path below the
251     * scripts directory</li>
252     * <li>Otherwise, treat the name as a relative path below the program
253     * directory</li>
254     * </ul>
255     * In any case, absolute pathnames will work.
256     *
257     * @param profile the Profile to use as a base.
258     * @param pName   the name, possibly starting with home:, profile:,
259     *                program:, preference:, scripts:, or settings:
260     * @return Absolute file name to use, or null. This will include
261     *         system-specific file separators.
262     * @since 4.17.3
263     */
264    @Nonnull
265    @CheckReturnValue
266    static public String getExternalFilename(@CheckForNull Profile profile, @Nonnull String pName) {
267        return FileUtilSupport.getDefault().getExternalFilename(profile, pName);
268    }
269
270    /**
271     * Convert a portable filename into an absolute filename, using
272     * {@link ProfileManager#getActiveProfile()} as the base.
273     *
274     * @param path the portable filename
275     * @return An absolute filename
276     */
277    @Nonnull
278    @CheckReturnValue
279    static public String getAbsoluteFilename(@Nonnull String path) {
280        return FileUtilSupport.getDefault().getAbsoluteFilename(path);
281    }
282
283    /**
284     * Convert a portable filename into an absolute filename.
285     *
286     * @param profile the profile to use the base
287     * @param path    the portable filename
288     * @return An absolute filename
289     */
290    @Nonnull
291    @CheckReturnValue
292    static public String getAbsoluteFilename(@CheckForNull Profile profile, @Nonnull String path) {
293        return FileUtilSupport.getDefault().getAbsoluteFilename(profile, path);
294    }
295
296    /**
297     * Convert a File object's path to our preferred storage form.
298     * <p>
299     * This is the inverse of {@link #getFile(String pName)}. Deprecated forms
300     * are not created.
301     *
302     * @param file File at path to be represented
303     * @return Filename for storage in a portable manner. This will include
304     *         portable, not system-specific, file separators.
305     * @since 2.7.2
306     */
307    @Nonnull
308    @CheckReturnValue
309    static public String getPortableFilename(@Nonnull File file) {
310        return FileUtilSupport.getDefault().getPortableFilename(file);
311    }
312
313    /**
314     * Convert a File object's path to our preferred storage form.
315     * <p>
316     * This is the inverse of {@link #getFile(String pName)}. Deprecated forms
317     * are not created.
318     * <p>
319     * This method supports a specific use case concerning profiles and other
320     * portable paths that are stored within the User files directory, which
321     * will cause the {@link jmri.profile.ProfileManager} to write an incorrect
322     * path for the current profile or
323     * {@link apps.configurexml.FileLocationPaneXml} to write an incorrect path
324     * for the Users file directory. In most cases, the use of
325     * {@link #getPortableFilename(java.io.File)} is preferable.
326     *
327     * @param file                File at path to be represented
328     * @param ignoreUserFilesPath true if paths in the User files path should be
329     *                            stored as absolute paths, which is often not
330     *                            desirable.
331     * @param ignoreProfilePath   true if paths in the profile should be stored
332     *                            as absolute paths, which is often not
333     *                            desirable.
334     * @return Storage format representation
335     * @since 3.5.5
336     */
337    @Nonnull
338    @CheckReturnValue
339    static public String getPortableFilename(@Nonnull File file, boolean ignoreUserFilesPath, boolean ignoreProfilePath) {
340        return FileUtilSupport.getDefault().getPortableFilename(file, ignoreUserFilesPath, ignoreProfilePath);
341    }
342
343    /**
344     * Convert a filename string to our preferred storage form.
345     * <p>
346     * This is the inverse of {@link #getExternalFilename(String pName)}.
347     * Deprecated forms are not created.
348     *
349     * @param filename Filename to be represented
350     * @return Filename for storage in a portable manner
351     * @since 2.7.2
352     */
353    @Nonnull
354    @CheckReturnValue
355    static public String getPortableFilename(@Nonnull String filename) {
356        return FileUtilSupport.getDefault().getPortableFilename(filename);
357    }
358
359    /**
360     * Convert a filename string to our preferred storage form.
361     * <p>
362     * This is the inverse of {@link #getExternalFilename(String pName)}.
363     * Deprecated forms are not created.
364     * <p>
365     * This method supports a specific use case concerning profiles and other
366     * portable paths that are stored within the User files directory, which
367     * will cause the {@link jmri.profile.ProfileManager} to write an incorrect
368     * path for the current profile or
369     * {@link apps.configurexml.FileLocationPaneXml} to write an incorrect path
370     * for the Users file directory. In most cases, the use of
371     * {@link #getPortableFilename(java.io.File)} is preferable.
372     *
373     * @param filename            Filename to be represented
374     * @param ignoreUserFilesPath true if paths in the User files path should be
375     *                            stored as absolute paths, which is often not
376     *                            desirable.
377     * @param ignoreProfilePath   true if paths in the profile path should be
378     *                            stored as absolute paths, which is often not
379     *                            desirable.
380     * @return Storage format representation
381     * @since 3.5.5
382     */
383    @Nonnull
384    @CheckReturnValue
385    static public String getPortableFilename(@Nonnull String filename, boolean ignoreUserFilesPath, boolean ignoreProfilePath) {
386        return FileUtilSupport.getDefault().getPortableFilename(filename, ignoreUserFilesPath, ignoreProfilePath);
387    }
388
389    /**
390     * Convert a File object's path to our preferred storage form.
391     * <p>
392     * This is the inverse of {@link #getFile(String pName)}. Deprecated forms
393     * are not created.
394     *
395     * @param profile Profile to use as a base
396     * @param file    File at path to be represented
397     * @return Filename for storage in a portable manner. This will include
398     *         portable, not system-specific, file separators.
399     * @since 4.17.3
400     */
401    @Nonnull
402    @CheckReturnValue
403    static public String getPortableFilename(@CheckForNull Profile profile, @Nonnull File file) {
404        return FileUtilSupport.getDefault().getPortableFilename(profile, file);
405    }
406
407    /**
408     * Convert a File object's path to our preferred storage form.
409     * <p>
410     * This is the inverse of {@link #getFile(String pName)}. Deprecated forms
411     * are not created.
412     * <p>
413     * This method supports a specific use case concerning profiles and other
414     * portable paths that are stored within the User files directory, which
415     * will cause the {@link jmri.profile.ProfileManager} to write an incorrect
416     * path for the current profile or
417     * {@link apps.configurexml.FileLocationPaneXml} to write an incorrect path
418     * for the Users file directory. In most cases, the use of
419     * {@link #getPortableFilename(java.io.File)} is preferable.
420     *
421     * @param profile             Profile to use as a base
422     * @param file                File at path to be represented
423     * @param ignoreUserFilesPath true if paths in the User files path should be
424     *                            stored as absolute paths, which is often not
425     *                            desirable.
426     * @param ignoreProfilePath   true if paths in the profile should be stored
427     *                            as absolute paths, which is often not
428     *                            desirable.
429     * @return Storage format representation
430     * @since 4.17.3
431     */
432    @Nonnull
433    @CheckReturnValue
434    static public String getPortableFilename(@CheckForNull Profile profile, @Nonnull File file, boolean ignoreUserFilesPath,
435            boolean ignoreProfilePath) {
436        return FileUtilSupport.getDefault().getPortableFilename(profile, file, ignoreUserFilesPath, ignoreProfilePath);
437    }
438
439    /**
440     * Convert a filename string to our preferred storage form.
441     * <p>
442     * This is the inverse of {@link #getExternalFilename(String pName)}.
443     * Deprecated forms are not created.
444     *
445     * @param profile  the Profile to use as a base
446     * @param filename Filename to be represented
447     * @return Filename for storage in a portable manner
448     * @since 4.17.3
449     */
450    @Nonnull
451    @CheckReturnValue
452    static public String getPortableFilename(@CheckForNull Profile profile, @Nonnull String filename) {
453        return FileUtilSupport.getDefault().getPortableFilename(profile, filename);
454    }
455
456    /**
457     * Convert a filename string to our preferred storage form.
458     * <p>
459     * This is the inverse of {@link #getExternalFilename(String pName)}.
460     * Deprecated forms are not created.
461     * <p>
462     * This method supports a specific use case concerning profiles and other
463     * portable paths that are stored within the User files directory, which
464     * will cause the {@link jmri.profile.ProfileManager} to write an incorrect
465     * path for the current profile or
466     * {@link apps.configurexml.FileLocationPaneXml} to write an incorrect path
467     * for the Users file directory. In most cases, the use of
468     * {@link #getPortableFilename(java.io.File)} is preferable.
469     *
470     * @param profile             the profile to use as a base
471     * @param filename            Filename to be represented
472     * @param ignoreUserFilesPath true if paths in the User files path should be
473     *                            stored as absolute paths, which is often not
474     *                            desirable.
475     * @param ignoreProfilePath   true if paths in the profile path should be
476     *                            stored as absolute paths, which is often not
477     *                            desirable.
478     * @return Storage format representation
479     * @since 4.17.3
480     */
481    @Nonnull
482    @CheckReturnValue
483    static public String getPortableFilename(@CheckForNull Profile profile, @Nonnull String filename,
484            boolean ignoreUserFilesPath, boolean ignoreProfilePath) {
485        return FileUtilSupport.getDefault().getPortableFilename(profile, filename, ignoreUserFilesPath, ignoreProfilePath);
486    }
487
488    /**
489     * Test if the given filename is a portable filename.
490     *
491     * @param filename the name to test
492     * @return true if filename is portable
493     */
494    static public boolean isPortableFilename(@Nonnull String filename) {
495        return FileUtilSupport.getDefault().isPortableFilename(filename);
496    }
497
498    /**
499     * Get the user's home directory.
500     *
501     * @return User's home directory as a String
502     */
503    @Nonnull
504    @CheckReturnValue
505    static public String getHomePath() {
506        return FileUtilSupport.getDefault().getHomePath();
507    }
508
509    /**
510     * Get the user's files directory. If not set by the user, this is the same
511     * as the profile path for the Profile specified by
512     * {@link ProfileManager#getActiveProfile()}.
513     *
514     * @see #getProfilePath()
515     * @return User's files directory as a String
516     */
517    @Nonnull
518    @CheckReturnValue
519    static public String getUserFilesPath() {
520        return FileUtilSupport.getDefault().getUserFilesPath();
521    }
522
523    /**
524     * Get the user's files directory. If not set by the user, this is the same
525     * as the profile path.
526     *
527     * @param profile the profile to use as a base
528     * @see #getProfilePath()
529     * @return User's files directory as a String
530     */
531    @Nonnull
532    @CheckReturnValue
533    static public String getUserFilesPath(@CheckForNull Profile profile) {
534        return FileUtilSupport.getDefault().getUserFilesPath(profile);
535    }
536
537    /**
538     * Set the user's files directory.
539     *
540     * @see #getUserFilesPath()
541     * @param profile The profile to use as a base
542     * @param path    The path to the user's files directory
543     */
544    static public void setUserFilesPath(@CheckForNull Profile profile, @Nonnull String path) {
545        FileUtilSupport.getDefault().setUserFilesPath(profile, path);
546    }
547
548    /**
549     * Get the profile directory. Uses the Profile returned by
550     * {@link ProfileManager#getActiveProfile()} as a base. If that is null,
551     * gets the preferences path.
552     *
553     * @see #getPreferencesPath()
554     * @return Profile directory
555     */
556    @Nonnull
557    @CheckReturnValue
558    static public String getProfilePath() {
559        return FileUtilSupport.getDefault().getProfilePath();
560    }
561
562    /**
563     * Get the profile directory. If the profile is null or has a null
564     * directory, this is the same as the preferences path.
565     *
566     * @param profile the profile to use as a base
567     * @see #getPreferencesPath()
568     * @return Profile directory
569     */
570    @Nonnull
571    @CheckReturnValue
572    static public String getProfilePath(@CheckForNull Profile profile) {
573        return FileUtilSupport.getDefault().getProfilePath(profile);
574    }
575
576    /**
577     * Get the preferences directory. This directory is set based on the OS and
578     * is not normally settable by the user.
579     * <ul>
580     * <li>On Microsoft Windows systems, this is {@code JMRI} in the User's home
581     * directory.</li>
582     * <li>On OS X systems, this is {@code Library/Preferences/JMRI} in the
583     * User's home directory.</li>
584     * <li>On Linux, Solaris, and other UNIXes, this is {@code .jmri} in the
585     * User's home directory.</li>
586     * <li>This can be overridden with by setting the {@code jmri.prefsdir} Java
587     * property when starting JMRI.</li>
588     * </ul>
589     * Use {@link #getHomePath()} to get the User's home directory.
590     *
591     * @see #getHomePath()
592     * @return Path to the preferences directory.
593     */
594    @Nonnull
595    @CheckReturnValue
596    static public String getPreferencesPath() {
597        return FileUtilSupport.getDefault().getPreferencesPath();
598    }
599
600    /**
601     * Get the JMRI program directory. If the program directory has not been
602     * previously sets, first sets the program directory to the value specified
603     * in the Java System property <code>jmri.path.program</code>, or
604     * <code>.</code> if that property is not set.
605     *
606     * @return JMRI program directory as a String.
607     */
608    @Nonnull
609    @CheckReturnValue
610    static public String getProgramPath() {
611        return FileUtilSupport.getDefault().getProgramPath();
612    }
613
614    /**
615     * Set the JMRI program directory.
616     * <p>
617     * Convenience method that calls
618     * {@link FileUtil#setProgramPath(java.io.File)} with the passed in path.
619     *
620     * @param path the path to the JMRI installation
621     */
622    static public void setProgramPath(@Nonnull String path) {
623        FileUtilSupport.getDefault().setProgramPath(new File(path));
624    }
625
626    /**
627     * Set the JMRI program directory.
628     * <p>
629     * If set, allows JMRI to be loaded from locations other than the directory
630     * containing JMRI resources. This must be set very early in the process of
631     * loading JMRI (prior to loading any other JMRI code) to be meaningfully
632     * used.
633     *
634     * @param path the path to the JMRI installation
635     */
636    static public void setProgramPath(@Nonnull File path) {
637        FileUtilSupport.getDefault().setProgramPath(path);
638    }
639
640    /**
641     * Get the URL of a portable filename if it can be located using
642     * {@link #findURL(java.lang.String)}
643     *
644     * @param path the path to find
645     * @return URL of portable or absolute path
646     */
647    @Nonnull
648    @CheckReturnValue
649    static public URI findExternalFilename(@Nonnull String path) {
650        return FileUtilSupport.getDefault().findExternalFilename(path);
651    }
652
653    /**
654     * Search for a file or JAR resource by name and return the
655     * {@link java.io.InputStream} for that file. Search order is defined by
656     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...) }.
657     * No limits are placed on search locations.
658     *
659     * @param path The relative path of the file or resource
660     * @return InputStream or null.
661     * @see #findInputStream(java.lang.String, java.lang.String...)
662     * @see #findInputStream(java.lang.String, jmri.util.FileUtil.Location,
663     * java.lang.String...)
664     * @see #findURL(java.lang.String)
665     * @see #findURL(java.lang.String, java.lang.String...)
666     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location,
667     * java.lang.String...)
668     */
669    static public InputStream findInputStream(@Nonnull String path) {
670        return FileUtilSupport.getDefault().findInputStream(path);
671    }
672
673    /**
674     * Search for a file or JAR resource by name and return the
675     * {@link java.io.InputStream} for that file. Search order is defined by
676     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...) }.
677     * No limits are placed on search locations.
678     *
679     * @param path        The relative path of the file or resource
680     * @param searchPaths a list of paths to search for the path in
681     * @return InputStream or null.
682     * @see #findInputStream(java.lang.String)
683     * @see #findInputStream(java.lang.String, jmri.util.FileUtil.Location,
684     * java.lang.String...)
685     */
686    static public InputStream findInputStream(@Nonnull String path, @Nonnull String... searchPaths) {
687        return FileUtilSupport.getDefault().findInputStream(path, searchPaths);
688    }
689
690    /**
691     * Search for a file or JAR resource by name and return the
692     * {@link java.io.InputStream} for that file. Search order is defined by
693     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...) }.
694     *
695     * @param path      The relative path of the file or resource
696     * @param locations The type of locations to limit the search to
697     * @return InputStream or null.
698     * @see #findInputStream(java.lang.String)
699     * @see #findInputStream(java.lang.String, jmri.util.FileUtil.Location,
700     * java.lang.String...)
701     */
702    static public InputStream findInputStream(@Nonnull String path, Location locations) {
703        return FileUtilSupport.getDefault().findInputStream(path, locations);
704    }
705
706    /**
707     * Search for a file or JAR resource by name and return the
708     * {@link java.io.InputStream} for that file. Search order is defined by
709     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...) }.
710     *
711     * @param path        The relative path of the file or resource
712     * @param locations   The type of locations to limit the search to
713     * @param searchPaths a list of paths to search for the path in
714     * @return InputStream or null.
715     * @see #findInputStream(java.lang.String)
716     * @see #findInputStream(java.lang.String, java.lang.String...)
717     */
718    static public InputStream findInputStream(@Nonnull String path, Location locations, @Nonnull String... searchPaths) {
719        return FileUtilSupport.getDefault().findInputStream(path, locations, searchPaths);
720    }
721
722    /**
723     * Get the resources directory within the user's files directory.
724     *
725     * @return path to [user's file]/resources/
726     */
727    @Nonnull
728    @CheckReturnValue
729    static public String getUserResourcePath() {
730        return FileUtilSupport.getDefault().getUserResourcePath();
731    }
732
733    /**
734     * Search for a file or JAR resource by name and return the
735     * {@link java.net.URI} for that file. Search order is defined by
736     * {@link #findURI(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
737     * No limits are placed on search locations.
738     *
739     * @param path The relative path of the file or resource.
740     * @return The URI or null.
741     * @see #findURI(java.lang.String, java.lang.String...)
742     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location)
743     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location,
744     * java.lang.String...)
745     */
746    static public URI findURI(@Nonnull String path) {
747        return FileUtilSupport.getDefault().findURI(path);
748    }
749
750    /**
751     * Search for a file or JAR resource by name and return the
752     * {@link java.net.URI} for that file. Search order is defined by
753     * {@link #findURI(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
754     * No limits are placed on search locations.
755     * <p>
756     * Note that if the file for path is not found in one of the searchPaths,
757     * all standard locations are also be searched through to find the file. If
758     * you need to limit the locations where the file can be found use
759     * {@link #findURI(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
760     *
761     * @param path        The relative path of the file or resource
762     * @param searchPaths a list of paths to search for the path in
763     * @return The URI or null
764     * @see #findURI(java.lang.String)
765     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location)
766     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location,
767     * java.lang.String...)
768     */
769    static public URI findURI(@Nonnull String path, @Nonnull String... searchPaths) {
770        return FileUtilSupport.getDefault().findURI(path, searchPaths);
771    }
772
773    /**
774     * Search for a file or JAR resource by name and return the
775     * {@link java.net.URI} for that file. Search order is defined by
776     * {@link #findURI(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
777     *
778     * @param path      The relative path of the file or resource
779     * @param locations The types of locations to limit the search to
780     * @return The URI or null
781     * @see #findURI(java.lang.String)
782     * @see #findURI(java.lang.String, java.lang.String...)
783     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location,
784     * java.lang.String...)
785     */
786    static public URI findURI(@Nonnull String path, @Nonnull Location locations) {
787        return FileUtilSupport.getDefault().findURI(path, locations);
788    }
789
790    /**
791     * Search for a file or JAR resource by name and return the
792     * {@link java.net.URI} for that file.
793     * <p>
794     * Search order is:
795     * <ol>
796     * <li>For any provided searchPaths, iterate over the searchPaths by
797     * prepending each searchPath to the path and following the following search
798     * order:<ol>
799     * <li>As a {@link java.io.File} in the user preferences directory</li>
800     * <li>As a File in the current working directory (usually, but not always
801     * the JMRI distribution directory)</li>
802     * <li>As a File in the JMRI distribution directory</li>
803     * <li>As a resource in jmri.jar</li>
804     * </ol></li>
805     * <li>If the file or resource has not been found in the searchPaths, search
806     * in the four locations listed without prepending any path</li>
807     * <li>As a File with an absolute path</li>
808     * </ol>
809     * <p>
810     * The <code>locations</code> parameter limits the above logic by limiting
811     * the location searched.
812     * <ol>
813     * <li>{@link Location#ALL} will not place any limits on the search</li>
814     * <li>{@link Location#NONE} effectively requires that <code>path</code> be
815     * a portable pathname</li>
816     * <li>{@link Location#INSTALLED} limits the search to the
817     * {@link FileUtil#PROGRAM} directory and JARs in the class path</li>
818     * <li>{@link Location#USER} limits the search to the
819     * {@link FileUtil#PREFERENCES}, {@link FileUtil#PROFILE}, and
820     * {@link FileUtil#SETTINGS} directories (in that order)</li>
821     * </ol>
822     *
823     * @param path        The relative path of the file or resource
824     * @param locations   The types of locations to limit the search to
825     * @param searchPaths a list of paths to search for the path in
826     * @return The URI or null
827     * @see #findURI(java.lang.String)
828     * @see #findURI(java.lang.String, jmri.util.FileUtil.Location)
829     * @see #findURI(java.lang.String, java.lang.String...)
830     */
831    static public URI findURI(@Nonnull String path, @Nonnull Location locations, @Nonnull String... searchPaths) {
832        return FileUtilSupport.getDefault().findURI(path, locations, searchPaths);
833    }
834
835    /**
836     * Search for a file or JAR resource by name and return the
837     * {@link java.net.URL} for that file. Search order is defined by
838     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
839     * No limits are placed on search locations.
840     * <p>
841     * TODO: add @CheckForNull annotation / fix Possible null pointers.
842     *
843     * @param path The relative path of the file or resource.
844     * @return The URL or null.
845     * @see #findURL(java.lang.String, java.lang.String...)
846     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location)
847     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location,
848     * java.lang.String...)
849     */
850    static public URL findURL(@Nonnull String path) {
851        return FileUtilSupport.getDefault().findURL(path);
852    }
853
854    /**
855     * Search for a file or JAR resource by name and return the
856     * {@link java.net.URL} for that file. Search order is defined by
857     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
858     * No limits are placed on search locations.
859     *
860     * @param path        The relative path of the file or resource
861     * @param searchPaths a list of paths to search for the path in
862     * @return The URL or null
863     * @see #findURL(java.lang.String)
864     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location)
865     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location,
866     * java.lang.String...)
867     */
868    static public URL findURL(@Nonnull String path, @Nonnull String... searchPaths) {
869        return FileUtilSupport.getDefault().findURL(path, searchPaths);
870    }
871
872    /**
873     * Search for a file or JAR resource by name and return the
874     * {@link java.net.URL} for that file. Search order is defined by
875     * {@link #findURL(java.lang.String, jmri.util.FileUtil.Location, java.lang.String...)}.
876     *
877     * @param path      The relative path of the file or resource
878     * @param locations The types of locations to limit the search to
879     * @return The URL or null
880     * @see #findURL(java.lang.String)
881     * @see #findURL(java.lang.String, java.lang.String...)
882     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location,
883     * java.lang.String...)
884     */
885    static public URL findURL(@Nonnull String path, @Nonnull Location locations) {
886        return FileUtilSupport.getDefault().findURL(path, locations);
887    }
888
889    /**
890     * Search for a file or JAR resource by name and return the
891     * {@link java.net.URL} for that file.
892     * <p>
893     * Search order is:
894     * <ol><li>For any provided searchPaths, iterate over the searchPaths by
895     * prepending each searchPath to the path and following the following search
896     * order:
897     * <ol><li>As a {@link java.io.File} in the user preferences directory</li>
898     * <li>As a File in the current working directory (usually, but not always
899     * the JMRI distribution directory)</li> <li>As a File in the JMRI
900     * distribution directory</li> <li>As a resource in jmri.jar</li></ol></li>
901     * <li>If the file or resource has not been found in the searchPaths, search
902     * in the four locations listed without prepending any path</li></ol>
903     * <p>
904     * The <code>locations</code> parameter limits the above logic by limiting
905     * the location searched.
906     * <ol><li>{@link Location#ALL} will not place any limits on the
907     * search</li><li>{@link Location#NONE} effectively requires that
908     * <code>path</code> be a portable
909     * pathname</li><li>{@link Location#INSTALLED} limits the search to the
910     * {@link #PROGRAM} directory and JARs in the class
911     * path</li><li>{@link Location#USER} limits the search to the
912     * {@link #PROFILE} directory</li></ol>
913     *
914     * @param path        The relative path of the file or resource
915     * @param locations   The types of locations to limit the search to
916     * @param searchPaths a list of paths to search for the path in
917     * @return The URL or null
918     * @see #findURL(java.lang.String)
919     * @see #findURL(java.lang.String, jmri.util.FileUtil.Location)
920     * @see #findURL(java.lang.String, java.lang.String...)
921     */
922    static public URL findURL(@Nonnull String path, @Nonnull Location locations, @Nonnull String... searchPaths) {
923        return FileUtilSupport.getDefault().findURL(path, locations, searchPaths);
924    }
925
926    /**
927     * Return the {@link java.net.URI} for a given URL
928     *
929     * @param url the URL
930     * @return a URI or null if the conversion would have caused a
931     *         {@link java.net.URISyntaxException}
932     */
933    static public URI urlToURI(@Nonnull URL url) {
934        return FileUtilSupport.getDefault().urlToURI(url);
935    }
936
937    /**
938     * Return the {@link java.net.URL} for a given {@link java.io.File}. This
939     * method catches a {@link java.net.MalformedURLException} and returns null
940     * in its place, since we really do not expect a File object to ever give a
941     * malformed URL. This method exists solely so implementing classes do not
942     * need to catch that exception.
943     *
944     * @param file The File to convert.
945     * @return a URL or null if the conversion would have caused a
946     *         MalformedURLException
947     */
948    static public URL fileToURL(@Nonnull File file) {
949        return FileUtilSupport.getDefault().fileToURL(file);
950    }
951
952    /**
953     * Get the JMRI distribution jar file.
954     *
955     * @return the JAR file containing the JMRI library or null if not running
956     *         from a JAR file
957     */
958    static public JarFile jmriJarFile() {
959        return FileUtilSupport.getDefault().getJmriJarFile();
960    }
961
962    /**
963     * Log all paths at the INFO level.
964     */
965    static public void logFilePaths() {
966        FileUtilSupport.getDefault().logFilePaths();
967    }
968
969    /**
970     * Get the path to the scripts directory using the Profile returned by
971     * {@link ProfileManager#getActiveProfile()} as the base.
972     *
973     * @return the scriptsPath
974     */
975    @Nonnull
976    @CheckReturnValue
977    public static String getScriptsPath() {
978        return FileUtilSupport.getDefault().getScriptsPath();
979    }
980
981    /**
982     * Get the path to the scripts directory.
983     *
984     * @param profile the Profile to use as the base
985     * @return the scriptsPath
986     */
987    @Nonnull
988    @CheckReturnValue
989    public static String getScriptsPath(@CheckForNull Profile profile) {
990        return FileUtilSupport.getDefault().getScriptsPath(profile);
991    }
992
993    /**
994     * Set the path to python scripts.
995     *
996     * @param profile the profile to set the path for
997     * @param path    the scriptsPath to set
998     */
999    public static void setScriptsPath(@CheckForNull Profile profile, @CheckForNull String path) {
1000        FileUtilSupport.getDefault().setScriptsPath(profile, path);
1001    }
1002
1003    /**
1004     * Read a text file into a String.
1005     *
1006     * @param file The text file.
1007     * @return The contents of the file.
1008     * @throws java.io.IOException if the file cannot be read
1009     */
1010    public static String readFile(@Nonnull File file) throws IOException {
1011        return FileUtil.readURL(FileUtil.fileToURL(file));
1012    }
1013
1014    /**
1015     * Read a text URL into a String. Would be significantly simpler with Java
1016     * 7. File is assumed to be encoded using UTF-8
1017     *
1018     * @param url The text URL.
1019     * @return The contents of the file.
1020     * @throws java.io.IOException if the URL cannot be read
1021     */
1022    public static String readURL(@Nonnull URL url) throws IOException {
1023        return FileUtilSupport.getDefault().readURL(url);
1024    }
1025
1026    /**
1027     * Replaces most non-alphanumeric characters in name with an underscore.
1028     *
1029     * @param name The filename to be sanitized.
1030     * @return The sanitized filename.
1031     */
1032    @Nonnull
1033    public static String sanitizeFilename(@Nonnull String name) {
1034        return FileUtilSupport.getDefault().sanitizeFilename(name);
1035    }
1036
1037    /**
1038     * Create a directory if required. Any parent directories will also be
1039     * created.
1040     *
1041     * @param path directory to create
1042     */
1043    public static void createDirectory(@Nonnull String path) {
1044        FileUtilSupport.getDefault().createDirectory(path);
1045    }
1046
1047    /**
1048     * Create a directory if required. Any parent directories will also be
1049     * created.
1050     *
1051     * @param dir directory to create
1052     */
1053    public static void createDirectory(@Nonnull File dir) {
1054        FileUtilSupport.getDefault().createDirectory(dir);
1055    }
1056
1057    /**
1058     * Recursively delete a path. It is recommended to use
1059     * {@link java.nio.file.Files#delete(java.nio.file.Path)} or
1060     * {@link java.nio.file.Files#deleteIfExists(java.nio.file.Path)} for files.
1061     *
1062     * @param path path to delete
1063     * @return true if path was deleted, false otherwise
1064     */
1065    public static boolean delete(@Nonnull File path) {
1066        return FileUtilSupport.getDefault().delete(path);
1067    }
1068
1069    /**
1070     * Copy a file or directory. It is recommended to use
1071     * {@link java.nio.file.Files#copy(java.nio.file.Path, java.io.OutputStream)}
1072     * for files.
1073     *
1074     * @param source the file or directory to copy
1075     * @param dest   must be the file or directory, not the containing directory
1076     * @throws java.io.IOException if file cannot be copied
1077     */
1078    public static void copy(@Nonnull File source, @Nonnull File dest) throws IOException {
1079        FileUtilSupport.getDefault().copy(source, dest);
1080    }
1081
1082    /**
1083     * Simple helper method to just append a text string to the end of the given
1084     * filename. The file will be created if it does not exist.
1085     *
1086     * @param file File to append text to
1087     * @param text Text to append
1088     * @throws java.io.IOException if file cannot be written to
1089     */
1090    public static void appendTextToFile(@Nonnull File file, @Nonnull String text) throws IOException {
1091        FileUtilSupport.getDefault().appendTextToFile(file, text);
1092    }
1093
1094    /**
1095     * Backup a file. The backup is in the same location as the original file,
1096     * has the extension <code>.bak</code> appended to the file name, and up to
1097     * four revisions are retained. The lowest numbered revision is the most
1098     * recent.
1099     *
1100     * @param file the file to backup
1101     * @throws java.io.IOException if a backup cannot be created
1102     * @see jmri.util.FileUtilSupport#backup(java.io.File)
1103     */
1104    public static void backup(@Nonnull File file) throws IOException {
1105        FileUtilSupport.getDefault().backup(file);
1106    }
1107
1108    /**
1109     * Rotate a file and its backups, retaining only a set number of backups.
1110     *
1111     * @param file      the file to rotate
1112     * @param max       maximum number of backups to retain
1113     * @param extension The extension to use for the rotations. If null or an
1114     *                  empty string, the rotation number is used as the
1115     *                  extension.
1116     * @throws java.io.IOException      if a backup cannot be created
1117     * @throws IllegalArgumentException if max is less than one
1118     * @see jmri.util.FileUtilSupport#rotate(java.io.File, int,
1119     * java.lang.String)
1120     * @see jmri.util.FileUtilSupport#backup(java.io.File)
1121     */
1122    public static void rotate(@Nonnull File file, int max, @CheckForNull String extension) throws IOException {
1123        FileUtilSupport.getDefault().rotate(file, max, extension);
1124    }
1125
1126    /**
1127     * Get the default instance of FileUtilSupport.
1128     *
1129     * @return the default instance of FileUtilSupport
1130     */
1131    public static FileUtilSupport getDefault() {
1132        return FileUtilSupport.getDefault();
1133    }
1134
1135    /**
1136     * PropertyChangeEvents for properties that are Profile-specific use a
1137     * Property to enclose both the Profile and the value of the property.
1138     */
1139    public static class Property implements Map.Entry<Profile, String> {
1140
1141        private final Profile key;
1142        private final String value;
1143
1144        // package private
1145        Property(Profile key, String value) {
1146            this.key = key;
1147            this.value = value;
1148        }
1149
1150        @Override
1151        public Profile getKey() {
1152            return key;
1153        }
1154
1155        @Override
1156        public String getValue() {
1157            return value;
1158        }
1159
1160        @Override
1161        public String setValue(String value) {
1162            throw new UnsupportedOperationException("Immutable by design");
1163        }
1164
1165    }
1166
1167    /* Private default constructor to ensure it's not documented. */
1168    private FileUtil() {
1169    }
1170}