    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.io;
    018
    019import java.io.File;
    020import java.net.MalformedURLException;
    021import java.net.URI;
    022import java.net.URL;
    023import java.util.Arrays;
    024import java.util.Map;
    025
    026import org.apache.commons.configuration2.ex.ConfigurationException;
    027import org.apache.commons.lang3.ObjectUtils;
    028import org.apache.commons.lang3.StringUtils;
    029import org.apache.commons.logging.Log;
    030import org.apache.commons.logging.LogFactory;
    031
    032/**
    033 * <p>
    034 * A utility class providing helper methods related to locating files.
    035 * </p>
    036 * <p>
    037 * The methods of this class are used behind the scenes when retrieving
    038 * configuration files based on different criteria, e.g. URLs, files, or more
    039 * complex search strategies. They also implement functionality required by the
    040 * default {@link FileSystem} implementations. Most methods are intended to be
    041 * used internally only by other classes in the {@code io} package.
    042 * </p>
    043 *
    044 * @since 2.0
    045 */
    046public final class FileLocatorUtils
    047{
    048    /**
    049     * Constant for the default {@code FileSystem}. This file system is used by
    050     * operations of this class if no specific file system is provided. An
    051     * instance of {@link DefaultFileSystem} is used.
    052     */
    053    public static final FileSystem DEFAULT_FILE_SYSTEM =
    054            new DefaultFileSystem();
    055
    056    /**
    057     * Constant for the default {@code FileLocationStrategy}. This strategy is
    058     * used by the {@code locate()} method if the passed in {@code FileLocator}
    059     * does not define its own location strategy. The default location strategy
    060     * is roughly equivalent to the search algorithm used in version 1.x of
    061     * <em>Commons Configuration</em> (there it was hard-coded though). It
    062     * behaves in the following way when passed a {@code FileLocator}:
    063     * <ul>
    064     * <li>If the {@code FileLocator} has a defined URL, this URL is used as the
    065     * file's URL (without any further checks).</li>
    066     * <li>Otherwise, base path and file name stored in the {@code FileLocator}
    067     * are passed to the current {@code FileSystem}'s {@code locateFromURL()}
    068     * method. If this results in a URL, it is returned.</li>
    069     * <li>Otherwise, if the locator's file name is an absolute path to an
    070     * existing file, the URL of this file is returned.</li>
    071     * <li>Otherwise, the concatenation of base path and file name is
    072     * constructed. If this path points to an existing file, its URL is
    073     * returned.</li>
    074     * <li>Otherwise, a sub directory of the current user's home directory as
    075     * defined by the base path is searched for the referenced file. If the file
    076     * can be found there, its URL is returned.</li>
    077     * <li>Otherwise, the base path is ignored, and the file name is searched in
    078     * the current user's home directory. If the file can be found there, its
    079     * URL is returned.</li>
    080     * <li>Otherwise, a resource with the name of the locator's file name is
    081     * searched in the classpath. If it can be found, its URL is returned.</li>
    082     * <li>Otherwise, the strategy gives up and returns <b>null</b> indicating
    083     * that the file cannot be resolved.</li>
    084     * </ul>
    085     */
    086    public static final FileLocationStrategy DEFAULT_LOCATION_STRATEGY =
    087            initDefaultLocationStrategy();
    088
    089    /** Constant for the file URL protocol */
    090    private static final String FILE_SCHEME = "file:";
    091
    092    /** The logger.*/
    093    private static final Log LOG = LogFactory.getLog(FileLocatorUtils.class);
    094
    095    /** Property key for the base path. */
    096    private static final String PROP_BASE_PATH = "basePath";
    097
    098    /** Property key for the encoding. */
    099    private static final String PROP_ENCODING = "encoding";
    100
    101    /** Property key for the file name. */
    102    private static final String PROP_FILE_NAME = "fileName";
    103
    104    /** Property key for the file system. */
    105    private static final String PROP_FILE_SYSTEM = "fileSystem";
    106
    107    /** Property key for the location strategy. */
    108    private static final String PROP_STRATEGY = "locationStrategy";
    109
    110    /** Property key for the source URL. */
    111    private static final String PROP_SOURCE_URL = "sourceURL";
    112
    113    /**
    114     * Private constructor so that no instances can be created.
    115     */
    116    private FileLocatorUtils()
    117    {
    118    }
    119
    120    /**
    121     * Tries to convert the specified URL to a file object. If this fails,
    122     * <b>null</b> is returned.
    123     *
    124     * @param url the URL
    125     * @return the resulting file object
    126     */
    127    public static File fileFromURL(final URL url)
    128    {
    129        return FileUtils.toFile(url);
    130    }
    131
    132    /**
    133     * Returns an uninitialized {@code FileLocatorBuilder} which can be used
    134     * for the creation of a {@code FileLocator} object. This method provides
    135     * a convenient way to create file locators using a fluent API as in the
    136     * following example:
    137     * <pre>
    138     * FileLocator locator = FileLocatorUtils.fileLocator()
    139     *     .basePath(myBasePath)
    140     *     .fileName("test.xml")
    141     *     .create();
    142     * </pre>
    143     * @return a builder object for defining a {@code FileLocator}
    144     */
    145    public static FileLocator.FileLocatorBuilder fileLocator()
    146    {
    147        return fileLocator(null);
    148    }
    149
    150    /**
    151     * Returns a {@code FileLocatorBuilder} which is already initialized with
    152     * the properties of the passed in {@code FileLocator}. This builder can
    153     * be used to create a {@code FileLocator} object which shares properties
    154     * of the original locator (e.g. the {@code FileSystem} or the encoding),
    155     * but points to a different file. An example use case is as follows:
    156     * <pre>
    157     * FileLocator loc1 = ...
    158     * FileLocator loc2 = FileLocatorUtils.fileLocator(loc1)
    159     *     .setFileName("anotherTest.xml")
    160     *     .create();
    161     * </pre>
    162     * @param src the source {@code FileLocator} (may be <b>null</b>)
    163     * @return an initialized builder object for defining a {@code FileLocator}
    164     */
    165    public static FileLocator.FileLocatorBuilder fileLocator(final FileLocator src)
    166    {
    167        return new FileLocator.FileLocatorBuilder(src);
    168    }
    169
    170    /**
    171     * Creates a new {@code FileLocator} object with the properties defined in
    172     * the given map. The map must be conform to the structure generated by the
    173     * {@link #put(FileLocator, Map)} method; unexpected data can cause
    174     * {@code ClassCastException} exceptions. The map can be <b>null</b>, then
    175     * an uninitialized {@code FileLocator} is returned.
    176     *
    177     * @param map the map
    178     * @return the new {@code FileLocator}
    179     * @throws ClassCastException if the map contains invalid data
    180     */
    181    public static FileLocator fromMap(final Map<String, ?> map)
    182    {
    183        final FileLocator.FileLocatorBuilder builder = fileLocator();
    184        if (map != null)
    185        {
    186            builder.basePath((String) map.get(PROP_BASE_PATH))
    187                    .encoding((String) map.get(PROP_ENCODING))
    188                    .fileName((String) map.get(PROP_FILE_NAME))
    189                    .fileSystem((FileSystem) map.get(PROP_FILE_SYSTEM))
    190                    .locationStrategy(
    191                            (FileLocationStrategy) map.get(PROP_STRATEGY))
    192                    .sourceURL((URL) map.get(PROP_SOURCE_URL));
    193        }
    194        return builder.create();
    195    }
    196
    197    /**
    198     * Stores the specified {@code FileLocator} in the given map. With the
    199     * {@link #fromMap(Map)} method a new {@code FileLocator} with the same
    200     * properties as the original one can be created.
    201     *
    202     * @param locator the {@code FileLocator} to be stored
    203     * @param map the map in which to store the {@code FileLocator} (must not be
    204     *        <b>null</b>)
    205     * @throws IllegalArgumentException if the map is <b>null</b>
    206     */
    207    public static void put(final FileLocator locator, final Map<String, Object> map)
    208    {
    209        if (map == null)
    210        {
    211            throw new IllegalArgumentException("Map must not be null!");
    212        }
    213
    214        if (locator != null)
    215        {
    216            map.put(PROP_BASE_PATH, locator.getBasePath());
    217            map.put(PROP_ENCODING, locator.getEncoding());
    218            map.put(PROP_FILE_NAME, locator.getFileName());
    219            map.put(PROP_FILE_SYSTEM, locator.getFileSystem());
    220            map.put(PROP_SOURCE_URL, locator.getSourceURL());
    221            map.put(PROP_STRATEGY, locator.getLocationStrategy());
    222        }
    223    }
    224
    225    /**
    226     * Checks whether the specified {@code FileLocator} contains enough
    227     * information to locate a file. This is the case if a file name or a URL is
    228     * defined. If the passed in {@code FileLocator} is <b>null</b>, result is
    229     * <b>false</b>.
    230     *
    231     * @param locator the {@code FileLocator} to check
    232     * @return a flag whether a file location is defined by this
    233     *         {@code FileLocator}
    234     */
    235    public static boolean isLocationDefined(final FileLocator locator)
    236    {
    237        return locator != null
    238                && (locator.getFileName() != null || locator.getSourceURL() != null);
    239    }
    240
    241    /**
    242     * Returns a flag whether all components of the given {@code FileLocator}
    243     * describing the referenced file are defined. In order to reference a file,
    244     * it is not necessary that all components are filled in (for instance, the
    245     * URL alone is sufficient). For some use cases however, it might be of
    246     * interest to have different methods for accessing the referenced file.
    247     * Also, depending on the filled out properties, there is a subtle
    248     * difference how the file is accessed: If only the file name is set (and
    249     * optionally the base path), each time the file is accessed a
    250     * {@code locate()} operation has to be performed to uniquely identify the
    251     * file. If however the URL is determined once based on the other components
    252     * and stored in a fully defined {@code FileLocator}, it can be used
    253     * directly to identify the file. If the passed in {@code FileLocator} is
    254     * <b>null</b>, result is <b>false</b>.
    255     *
    256     * @param locator the {@code FileLocator} to be checked (may be <b>null</b>)
    257     * @return a flag whether all components describing the referenced file are
    258     *         initialized
    259     */
    260    public static boolean isFullyInitialized(final FileLocator locator)
    261    {
    262        if (locator == null)
    263        {
    264            return false;
    265        }
    266        return locator.getBasePath() != null && locator.getFileName() != null
    267                && locator.getSourceURL() != null;
    268    }
    269
    270    /**
    271     * Returns a {@code FileLocator} object based on the passed in one whose
    272     * location is fully defined. This method ensures that all components of the
    273     * {@code FileLocator} pointing to the file are set in a consistent way. In
    274     * detail it behaves as follows:
    275     * <ul>
    276     * <li>If the {@code FileLocator} has already all components set which
    277     * define the file, it is returned unchanged. <em>Note:</em> It is not
    278     * checked whether all components are really consistent!</li>
    279     * <li>{@link #locate(FileLocator)} is called to determine a unique URL
    280     * pointing to the referenced file. If this is successful, a new
    281     * {@code FileLocator} is created as a copy of the passed in one, but with
    282     * all components pointing to the file derived from this URL.</li>
    283     * <li>Otherwise, result is <b>null</b>.</li>
    284     * </ul>
    285     *
    286     * @param locator the {@code FileLocator} to be completed
    287     * @return a {@code FileLocator} with a fully initialized location if
    288     *         possible or <b>null</b>
    289     */
    290    public static FileLocator fullyInitializedLocator(final FileLocator locator)
    291    {
    292        if (isFullyInitialized(locator))
    293        {
    294            // already fully initialized
    295            return locator;
    296        }
    297
    298        final URL url = locate(locator);
    299        return url != null ? createFullyInitializedLocatorFromURL(locator,
    300                url) : null;
    301    }
    302
    303    /**
    304     * Locates the provided {@code FileLocator}, returning a URL for accessing
    305     * the referenced file. This method uses a {@link FileLocationStrategy} to
    306     * locate the file the passed in {@code FileLocator} points to. If the
    307     * {@code FileLocator} contains itself a {@code FileLocationStrategy}, it is
    308     * used. Otherwise, the default {@code FileLocationStrategy} is applied. The
    309     * strategy is passed the locator and a {@code FileSystem}. The resulting
    310     * URL is returned. If the {@code FileLocator} is <b>null</b>, result is
    311     * <b>null</b>.
    312     *
    313     * @param locator the {@code FileLocator} to be resolved
    314     * @return the URL pointing to the referenced file or <b>null</b> if the
    315     *         {@code FileLocator} could not be resolved
    316     * @see #DEFAULT_LOCATION_STRATEGY
    317     */
    318    public static URL locate(final FileLocator locator)
    319    {
    320        if (locator == null)
    321        {
    322            return null;
    323        }
    324
    325        return obtainLocationStrategy(locator).locate(
    326                obtainFileSystem(locator), locator);
    327    }
    328
    329    /**
    330     * Tries to locate the file referenced by the passed in {@code FileLocator}.
    331     * If this fails, an exception is thrown. This method works like
    332     * {@link #locate(FileLocator)}; however, in case of a failed location
    333     * attempt an exception is thrown.
    334     *
    335     * @param locator the {@code FileLocator} to be resolved
    336     * @return the URL pointing to the referenced file
    337     * @throws ConfigurationException if the file cannot be resolved
    338     */
    339    public static URL locateOrThrow(final FileLocator locator)
    340            throws ConfigurationException
    341    {
    342        final URL url = locate(locator);
    343        if (url == null)
    344        {
    345            throw new ConfigurationException("Could not locate: " + locator);
    346        }
    347        return url;
    348    }
    349
    350    /**
    351     * Return the path without the file name, for example http://xyz.net/foo/bar.xml
    352     * results in http://xyz.net/foo/
    353     *
    354     * @param url the URL from which to extract the path
    355     * @return the path component of the passed in URL
    356     */
    357    static String getBasePath(final URL url)
    358    {
    359        if (url == null)
    360        {
    361            return null;
    362        }
    363
    364        String s = url.toString();
    365        if (s.startsWith(FILE_SCHEME) && !s.startsWith("file://"))
    366        {
    367            s = "file://" + s.substring(FILE_SCHEME.length());
    368        }
    369
    370        if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
    371        {
    372            return s;
    373        }
    374        return s.substring(0, s.lastIndexOf("/") + 1);
    375    }
    376
    377    /**
    378     * Extract the file name from the specified URL.
    379     *
    380     * @param url the URL from which to extract the file name
    381     * @return the extracted file name
    382     */
    383    static String getFileName(final URL url)
    384    {
    385        if (url == null)
    386        {
    387            return null;
    388        }
    389
    390        final String path = url.getPath();
    391
    392        if (path.endsWith("/") || StringUtils.isEmpty(path))
    393        {
    394            return null;
    395        }
    396        return path.substring(path.lastIndexOf("/") + 1);
    397    }
    398
    399    /**
    400     * Tries to convert the specified base path and file name into a file object.
    401     * This method is called e.g. by the save() methods of file based
    402     * configurations. The parameter strings can be relative files, absolute
    403     * files and URLs as well. This implementation checks first whether the passed in
    404     * file name is absolute. If this is the case, it is returned. Otherwise
    405     * further checks are performed whether the base path and file name can be
    406     * combined to a valid URL or a valid file name. <em>Note:</em> The test
    407     * if the passed in file name is absolute is performed using
    408     * {@code java.io.File.isAbsolute()}. If the file name starts with a
    409     * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
    410     * Windows. So to ensure correct behavior for relative file names on all
    411     * platforms you should never let relative paths start with a slash. E.g.
    412     * in a configuration definition file do not use something like that:
    413     * <pre>
    414     * &lt;properties fileName="/subdir/my.properties"/&gt;
    415     * </pre>
    416     * Under Windows this path would be resolved relative to the configuration
    417     * definition file. Under Unix this would be treated as an absolute path
    418     * name.
    419     *
    420     * @param basePath the base path
    421     * @param fileName the file name (must not be <b>null</b>)
    422     * @return the file object (<b>null</b> if no file can be obtained)
    423     */
    424    static File getFile(final String basePath, final String fileName)
    425    {
    426        // Check if the file name is absolute
    427        final File f = new File(fileName);
    428        if (f.isAbsolute())
    429        {
    430            return f;
    431        }
    432
    433        // Check if URLs are involved
    434        URL url;
    435        try
    436        {
    437            url = new URL(new URL(basePath), fileName);
    438        }
    439        catch (final MalformedURLException mex1)
    440        {
    441            try
    442            {
    443                url = new URL(fileName);
    444            }
    445            catch (final MalformedURLException mex2)
    446            {
    447                url = null;
    448            }
    449        }
    450
    451        if (url != null)
    452        {
    453            return fileFromURL(url);
    454        }
    455
    456        return constructFile(basePath, fileName);
    457    }
    458
    459    /**
    460     * Convert the specified file into an URL. This method is equivalent
    461     * to file.toURI().toURL(). It was used to work around a bug in the JDK
    462     * preventing the transformation of a file into an URL if the file name
    463     * contains a '#' character. See the issue CONFIGURATION-300 for
    464     * more details. Now that we switched to JDK 1.4 we can directly use
    465     * file.toURI().toURL().
    466     *
    467     * @param file the file to be converted into an URL
    468     */
    469    static URL toURL(final File file) throws MalformedURLException
    470    {
    471        return file.toURI().toURL();
    472    }
    473
    474    /**
    475     * Tries to convert the specified URI to a URL. If this causes an exception,
    476     * result is <b>null</b>.
    477     *
    478     * @param uri the URI to be converted
    479     * @return the resulting URL or <b>null</b>
    480     */
    481    static URL convertURIToURL(final URI uri)
    482    {
    483        try
    484        {
    485            return uri.toURL();
    486        }
    487        catch (final MalformedURLException e)
    488        {
    489            return null;
    490        }
    491    }
    492
    493    /**
    494     * Tries to convert the specified file to a URL. If this causes an
    495     * exception, result is <b>null</b>.
    496     *
    497     * @param file the file to be converted
    498     * @return the resulting URL or <b>null</b>
    499     */
    500    static URL convertFileToURL(final File file)
    501    {
    502        return convertURIToURL(file.toURI());
    503    }
    504
    505    /**
    506     * Tries to find a resource with the given name in the classpath.
    507     *
    508     * @param resourceName the name of the resource
    509     * @return the URL to the found resource or <b>null</b> if the resource
    510     *         cannot be found
    511     */
    512    static URL locateFromClasspath(final String resourceName)
    513    {
    514        URL url = null;
    515        // attempt to load from the context classpath
    516        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
    517        if (loader != null)
    518        {
    519            url = loader.getResource(resourceName);
    520
    521            if (url != null)
    522            {
    523                LOG.debug("Loading configuration from the context classpath (" + resourceName + ")");
    524            }
    525        }
    526
    527        // attempt to load from the system classpath
    528        if (url == null)
    529        {
    530            url = ClassLoader.getSystemResource(resourceName);
    531
    532            if (url != null)
    533            {
    534                LOG.debug("Loading configuration from the system classpath (" + resourceName + ")");
    535            }
    536        }
    537        return url;
    538    }
    539
    540    /**
    541     * Helper method for constructing a file object from a base path and a
    542     * file name. This method is called if the base path passed to
    543     * {@code getURL()} does not seem to be a valid URL.
    544     *
    545     * @param basePath the base path
    546     * @param fileName the file name (must not be <b>null</b>)
    547     * @return the resulting file
    548     */
    549    static File constructFile(final String basePath, final String fileName)
    550    {
    551        File file;
    552
    553        final File absolute = new File(fileName);
    554        if (StringUtils.isEmpty(basePath) || absolute.isAbsolute())
    555        {
    556            file = absolute;
    557        }
    558        else
    559        {
    560            file = new File(appendPath(basePath, fileName));
    561        }
    562
    563        return file;
    564    }
    565
    566    /**
    567     * Extends a path by another component. The given extension is added to the
    568     * already existing path adding a separator if necessary.
    569     *
    570     * @param path the path to be extended
    571     * @param ext the extension of the path
    572     * @return the extended path
    573     */
    574    static String appendPath(final String path, final String ext)
    575    {
    576        final StringBuilder fName = new StringBuilder();
    577        fName.append(path);
    578
    579        // My best friend. Paranoia.
    580        if (!path.endsWith(File.separator))
    581        {
    582            fName.append(File.separator);
    583        }
    584
    585        //
    586        // We have a relative path, and we have
    587        // two possible forms here. If we have the
    588        // "./" form then just strip that off first
    589        // before continuing.
    590        //
    591        if (ext.startsWith("." + File.separator))
    592        {
    593            fName.append(ext.substring(2));
    594        }
    595        else
    596        {
    597            fName.append(ext);
    598        }
    599        return fName.toString();
    600    }
    601
    602    /**
    603     * Obtains a non-<b>null</b> {@code FileSystem} object from the passed in
    604     * {@code FileLocator}. If the passed in {@code FileLocator} has a
    605     * {@code FileSystem} object, it is returned. Otherwise, result is the
    606     * default {@code FileSystem}.
    607     *
    608     * @param locator the {@code FileLocator} (may be <b>null</b>)
    609     * @return the {@code FileSystem} to be used for this {@code FileLocator}
    610     */
    611    static FileSystem obtainFileSystem(final FileLocator locator)
    612    {
    613        return locator != null ? ObjectUtils.defaultIfNull(
    614                locator.getFileSystem(), DEFAULT_FILE_SYSTEM)
    615                : DEFAULT_FILE_SYSTEM;
    616    }
    617
    618    /**
    619     * Obtains a non <b>null</b> {@code FileLocationStrategy} object from the
    620     * passed in {@code FileLocator}. If the {@code FileLocator} is not
    621     * <b>null</b> and has a {@code FileLocationStrategy} defined, this strategy
    622     * is returned. Otherwise, result is the default
    623     * {@code FileLocationStrategy}.
    624     *
    625     * @param locator the {@code FileLocator}
    626     * @return the {@code FileLocationStrategy} for this {@code FileLocator}
    627     */
    628    static FileLocationStrategy obtainLocationStrategy(final FileLocator locator)
    629    {
    630        return locator != null ? ObjectUtils.defaultIfNull(
    631                locator.getLocationStrategy(), DEFAULT_LOCATION_STRATEGY)
    632                : DEFAULT_LOCATION_STRATEGY;
    633    }
    634
    635    /**
    636     * Creates a fully initialized {@code FileLocator} based on the specified
    637     * URL.
    638     *
    639     * @param src the source {@code FileLocator}
    640     * @param url the URL
    641     * @return the fully initialized {@code FileLocator}
    642     */
    643    private static FileLocator createFullyInitializedLocatorFromURL(final FileLocator src,
    644            final URL url)
    645    {
    646        final FileLocator.FileLocatorBuilder fileLocatorBuilder = fileLocator(src);
    647        if (src.getSourceURL() == null)
    648        {
    649            fileLocatorBuilder.sourceURL(url);
    650        }
    651        if (StringUtils.isBlank(src.getFileName()))
    652        {
    653            fileLocatorBuilder.fileName(getFileName(url));
    654        }
    655        if (StringUtils.isBlank(src.getBasePath()))
    656        {
    657            fileLocatorBuilder.basePath(getBasePath(url));
    658        }
    659        return fileLocatorBuilder.create();
    660    }
    661
    662    /**
    663     * Creates the default location strategy. This method creates a combined
    664     * location strategy as described in the comment of the
    665     * {@link #DEFAULT_LOCATION_STRATEGY} member field.
    666     *
    667     * @return the default {@code FileLocationStrategy}
    668     */
    669    private static FileLocationStrategy initDefaultLocationStrategy()
    670    {
    671        final FileLocationStrategy[] subStrategies =
    672                new FileLocationStrategy[] {
    673                        new ProvidedURLLocationStrategy(),
    674                        new FileSystemLocationStrategy(),
    675                        new AbsoluteNameLocationStrategy(),
    676                        new BasePathLocationStrategy(),
    677                        new HomeDirectoryLocationStrategy(true),
    678                        new HomeDirectoryLocationStrategy(false),
    679                        new ClasspathLocationStrategy()
    680                };
    681        return new CombinedLocationStrategy(Arrays.asList(subStrategies));
    682    }
    683}