You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/09/18 21:23:51 UTC

[commons-configuration] branch master updated (b1e5a14 -> cf1640d)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git.


    from b1e5a14  Remove trailing whitepace.
     new 34b7cdf  Sort members.
     new dd735ef  Sort members.
     new 0191351  Sort members.
     new 95744eb  Sort members.
     new bc15cb5  Sort members.
     new cf1640d  Sort members.

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../configuration2/PropertiesConfiguration.java    | 2200 ++++++++++----------
 .../builder/FileBasedBuilderParametersImpl.java    |  246 +--
 .../builder/FileBasedBuilderProperties.java        |   64 +-
 .../commons/configuration2/io/FileHandler.java     | 1518 +++++++-------
 .../commons/configuration2/io/FileLocator.java     |  388 ++--
 .../configuration2/io/FileLocatorUtils.java        |  522 ++---
 6 files changed, 2469 insertions(+), 2469 deletions(-)


[commons-configuration] 03/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit 0191351eddeeed91fc38c595435710d0bc1301fe
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:45:33 2020 -0400

    Sort members.
---
 .../commons/configuration2/io/FileHandler.java     | 1518 ++++++++++----------
 1 file changed, 759 insertions(+), 759 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/io/FileHandler.java b/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
index 9aee68e..8d87a9f 100644
--- a/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
+++ b/src/main/java/org/apache/commons/configuration2/io/FileHandler.java
@@ -118,6 +118,45 @@ import org.apache.commons.logging.LogFactory;
  */
 public class FileHandler
 {
+    /**
+     * An internal class that performs all update operations of the handler's
+     * {@code FileLocator} in a safe way even if there is concurrent access.
+     * This class implements anon-blocking algorithm for replacing the immutable
+     * {@code FileLocator} instance stored in an atomic reference by a
+     * manipulated instance. (If we already had lambdas, this could be done
+     * without a class in a more elegant way.)
+     */
+    private abstract class Updater
+    {
+        /**
+         * Performs an update of the enclosing file handler's
+         * {@code FileLocator} object.
+         */
+        public void update()
+        {
+            boolean done;
+            do
+            {
+                final FileLocator oldLocator = fileLocator.get();
+                final FileLocatorBuilder builder =
+                        FileLocatorUtils.fileLocator(oldLocator);
+                updateBuilder(builder);
+                done = fileLocator.compareAndSet(oldLocator, builder.create());
+            } while (!done);
+            fireLocationChangedEvent();
+        }
+
+        /**
+         * Updates the passed in builder object to apply the manipulation to be
+         * performed by this {@code Updater}. The builder has been setup with
+         * the former content of the {@code FileLocator} to be manipulated.
+         *
+         * @param builder the builder for creating an updated
+         *        {@code FileLocator}
+         */
+        protected abstract void updateBuilder(FileLocatorBuilder builder);
+    }
+
     /** Constant for the URI scheme for files. */
     private static final String FILE_SCHEME = "file:";
 
@@ -134,27 +173,133 @@ public class FileHandler
             new SynchronizerSupport()
             {
                 @Override
-                public void unlock(final LockMode mode)
+                public Synchronizer getSynchronizer()
                 {
+                    return NoOpSynchronizer.INSTANCE;
                 }
 
                 @Override
-                public void setSynchronizer(final Synchronizer sync)
+                public void lock(final LockMode mode)
                 {
                 }
 
                 @Override
-                public void lock(final LockMode mode)
+                public void setSynchronizer(final Synchronizer sync)
                 {
                 }
 
                 @Override
-                public Synchronizer getSynchronizer()
+                public void unlock(final LockMode mode)
                 {
-                    return NoOpSynchronizer.INSTANCE;
                 }
             };
 
+    /**
+     * Helper method for checking a file handler which is to be copied. Throws
+     * an exception if the handler is <b>null</b>.
+     *
+     * @param c the {@code FileHandler} from which to copy the location
+     * @return the same {@code FileHandler}
+     */
+    private static FileHandler checkSourceHandler(final FileHandler c)
+    {
+        if (c == null)
+        {
+            throw new IllegalArgumentException(
+                    "FileHandler to assign must not be null!");
+        }
+        return c;
+    }
+
+    /**
+     * A helper method for closing a stream. Occurring exceptions will be
+     * ignored.
+     *
+     * @param cl the stream to be closed (may be <b>null</b>)
+     */
+    private static void closeSilent(final Closeable cl)
+    {
+        try
+        {
+            if (cl != null)
+            {
+                cl.close();
+            }
+        }
+        catch (final IOException e)
+        {
+            LogFactory.getLog(FileHandler.class).warn("Exception when closing " + cl, e);
+        }
+    }
+
+    /**
+     * Creates a {@code File} object from the content of the given
+     * {@code FileLocator} object. If the locator is not defined, result is
+     * <b>null</b>.
+     *
+     * @param loc the {@code FileLocator}
+     * @return a {@code File} object pointing to the associated file
+     */
+    private static File createFile(final FileLocator loc)
+    {
+        if (loc.getFileName() == null && loc.getSourceURL() == null)
+        {
+            return null;
+        }
+        else if (loc.getSourceURL() != null)
+        {
+            return FileLocatorUtils.fileFromURL(loc.getSourceURL());
+        }
+        else
+        {
+            return FileLocatorUtils.getFile(loc.getBasePath(),
+                    loc.getFileName());
+        }
+    }
+
+    /**
+     * Creates an uninitialized file locator.
+     *
+     * @return the locator
+     */
+    private static FileLocator emptyFileLocator()
+    {
+        return FileLocatorUtils.fileLocator().create();
+    }
+
+    /**
+     * Creates a new {@code FileHandler} instance from properties stored in a
+     * map. This method tries to extract a {@link FileLocator} from the map. A
+     * new {@code FileHandler} is created based on this {@code FileLocator}.
+     *
+     * @param map the map (may be <b>null</b>)
+     * @return the newly created {@code FileHandler}
+     * @see FileLocatorUtils#fromMap(Map)
+     */
+    public static FileHandler fromMap(final Map<String, ?> map)
+    {
+        return new FileHandler(null, FileLocatorUtils.fromMap(map));
+    }
+
+    /**
+     * Normalizes URLs to files. Ensures that file URLs start with the correct
+     * protocol.
+     *
+     * @param fileName the string to be normalized
+     * @return the normalized file URL
+     */
+    private static String normalizeFileURL(String fileName)
+    {
+        if (fileName != null && fileName.startsWith(FILE_SCHEME)
+                && !fileName.startsWith(FILE_SCHEME_SLASH))
+        {
+            fileName =
+                    FILE_SCHEME_SLASH
+                            + fileName.substring(FILE_SCHEME.length());
+        }
+        return fileName;
+    }
+
     /** The file-based object managed by this handler. */
     private final FileBased content;
 
@@ -219,31 +364,6 @@ public class FileHandler
     }
 
     /**
-     * Creates a new {@code FileHandler} instance from properties stored in a
-     * map. This method tries to extract a {@link FileLocator} from the map. A
-     * new {@code FileHandler} is created based on this {@code FileLocator}.
-     *
-     * @param map the map (may be <b>null</b>)
-     * @return the newly created {@code FileHandler}
-     * @see FileLocatorUtils#fromMap(Map)
-     */
-    public static FileHandler fromMap(final Map<String, ?> map)
-    {
-        return new FileHandler(null, FileLocatorUtils.fromMap(map));
-    }
-
-    /**
-     * Returns the {@code FileBased} object associated with this
-     * {@code FileHandler}.
-     *
-     * @return the associated {@code FileBased} object
-     */
-    public final FileBased getContent()
-    {
-        return content;
-    }
-
-    /**
      * Adds a listener to this {@code FileHandler}. It is notified about
      * property changes and IO operations.
      *
@@ -260,219 +380,194 @@ public class FileHandler
     }
 
     /**
-     * Removes the specified listener from this object.
+     * Checks whether a content object is available. If not, an exception is
+     * thrown. This method is called whenever the content object is accessed.
      *
-     * @param l the listener to be removed
+     * @throws ConfigurationException if not content object is defined
      */
-    public void removeFileHandlerListener(final FileHandlerListener l)
+    private void checkContent() throws ConfigurationException
     {
-        listeners.remove(l);
+        if (getContent() == null)
+        {
+            throw new ConfigurationException("No content available!");
+        }
     }
 
     /**
-     * Return the name of the file. If only a URL is defined, the file name
-     * is derived from there.
+     * Checks whether a content object is available and returns the current
+     * {@code FileLocator}. If there is no content object, an exception is
+     * thrown. This is a typical operation to be performed before a load() or
+     * save() operation.
      *
-     * @return the file name
+     * @return the current {@code FileLocator} to be used for the calling
+     *         operation
      */
-    public String getFileName()
+    private FileLocator checkContentAndGetLocator()
+            throws ConfigurationException
     {
-        final FileLocator locator = getFileLocator();
-        if (locator.getFileName() != null)
-        {
-            return locator.getFileName();
-        }
-
-        if (locator.getSourceURL() != null)
-        {
-            return FileLocatorUtils.getFileName(locator.getSourceURL());
-        }
-
-        return null;
+        checkContent();
+        return getFileLocator();
     }
 
     /**
-     * Set the name of the file. The passed in file name can contain a relative
-     * path. It must be used when referring files with relative paths from
-     * classpath. Use {@code setPath()} to set a full qualified file name. The
-     * URL is set to <b>null</b> as it has to be determined anew based on the
-     * file name and the base path.
-     *
-     * @param fileName the name of the file
+     * Clears the location of this {@code FileHandler}. Afterwards this handler
+     * does not point to any valid file.
      */
-    public void setFileName(final String fileName)
+    public void clearLocation()
     {
-        final String name = normalizeFileURL(fileName);
         new Updater()
         {
             @Override
             protected void updateBuilder(final FileLocatorBuilder builder)
             {
-                builder.fileName(name);
-                builder.sourceURL(null);
+                builder.basePath(null).fileName(null).sourceURL(null);
             }
         }
         .update();
     }
 
     /**
-     * Return the base path. If no base path is defined, but a URL, the base
-     * path is derived from there.
+     * Creates a {@code FileLocator} which is a copy of the passed in one, but
+     * has the given file name set to reference the target file.
      *
-     * @return the base path
+     * @param fileName the file name
+     * @param locator the {@code FileLocator} to copy
+     * @return the manipulated {@code FileLocator} with the file name
      */
-    public String getBasePath()
+    private FileLocator createLocatorWithFileName(final String fileName,
+            final FileLocator locator)
     {
-        final FileLocator locator = getFileLocator();
-        if (locator.getBasePath() != null)
-        {
-            return locator.getBasePath();
-        }
+        return FileLocatorUtils.fileLocator(locator).sourceURL(null)
+                .fileName(fileName).create();
+    }
 
-        if (locator.getSourceURL() != null)
-        {
-            return FileLocatorUtils.getBasePath(locator.getSourceURL());
-        }
+    /**
+     * Obtains a {@code SynchronizerSupport} for the current content. If the
+     * content implements this interface, it is returned. Otherwise, result is a
+     * dummy object. This method is called before load and save operations. The
+     * returned object is used for synchronization.
+     *
+     * @return the {@code SynchronizerSupport} for synchronization
+     */
+    private SynchronizerSupport fetchSynchronizerSupport()
+    {
+        if (getContent() instanceof SynchronizerSupport)
+        {
+            return (SynchronizerSupport) getContent();
+        }
+        return DUMMY_SYNC_SUPPORT;
+    }
 
-        return null;
+    /**
+     * Notifies the registered listeners about a completed load operation.
+     */
+    private void fireLoadedEvent()
+    {
+        for (final FileHandlerListener l : listeners)
+        {
+            l.loaded(this);
+        }
     }
 
     /**
-     * Sets the base path. The base path is typically either a path to a
-     * directory or a URL. Together with the value passed to the
-     * {@code setFileName()} method it defines the location of the configuration
-     * file to be loaded. The strategies for locating the file are quite
-     * tolerant. For instance if the file name is already an absolute path or a
-     * fully defined URL, the base path will be ignored. The base path can also
-     * be a URL, in which case the file name is interpreted in this URL's
-     * context. If other methods are used for determining the location of the
-     * associated file (e.g. {@code setFile()} or {@code setURL()}), the base
-     * path is automatically set. Setting the base path using this method
-     * automatically sets the URL to <b>null</b> because it has to be
-     * determined anew based on the file name and the base path.
-     *
-     * @param basePath the base path.
+     * Notifies the registered listeners about the start of a load operation.
      */
-    public void setBasePath(final String basePath)
+    private void fireLoadingEvent()
     {
-        final String path = normalizeFileURL(basePath);
-        new Updater()
+        for (final FileHandlerListener l : listeners)
         {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.basePath(path);
-                builder.sourceURL(null);
-            }
+            l.loading(this);
         }
-        .update();
     }
 
     /**
-     * Returns the location of the associated file as a {@code File} object. If
-     * the base path is a URL with a protocol different than &quot;file&quot;,
-     * or the file is within a compressed archive, the return value will not
-     * point to a valid file object.
-     *
-     * @return the location as {@code File} object; this can be <b>null</b>
+     * Notifies the registered listeners about a property update.
      */
-    public File getFile()
+    private void fireLocationChangedEvent()
     {
-        return createFile(getFileLocator());
+        for (final FileHandlerListener l : listeners)
+        {
+            l.locationChanged(this);
+        }
     }
 
     /**
-     * Sets the location of the associated file as a {@code File} object. The
-     * passed in {@code File} is made absolute if it is not yet. Then the file's
-     * path component becomes the base path and its name component becomes the
-     * file name.
-     *
-     * @param file the location of the associated file
+     * Notifies the registered listeners about a completed save operation.
      */
-    public void setFile(final File file)
+    private void fireSavedEvent()
     {
-        final String fileName = file.getName();
-        final String basePath =
-                file.getParentFile() != null ? file.getParentFile()
-                        .getAbsolutePath() : null;
-        new Updater()
+        for (final FileHandlerListener l : listeners)
         {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.fileName(fileName).basePath(basePath).sourceURL(null);
-            }
+            l.saved(this);
         }
-        .update();
     }
 
     /**
-     * Returns the full path to the associated file. The return value is a valid
-     * {@code File} path only if this location is based on a file on the local
-     * disk. If the file was loaded from a packed archive, the returned value is
-     * the string form of the URL from which the file was loaded.
+     * Notifies the registered listeners about the start of a save operation.
+     */
+    private void fireSavingEvent()
+    {
+        for (final FileHandlerListener l : listeners)
+        {
+            l.saving(this);
+        }
+    }
+
+    /**
+     * Return the base path. If no base path is defined, but a URL, the base
+     * path is derived from there.
      *
-     * @return the full path to the associated file
+     * @return the base path
      */
-    public String getPath()
+    public String getBasePath()
     {
         final FileLocator locator = getFileLocator();
-        final File file = createFile(locator);
-        return FileLocatorUtils.obtainFileSystem(locator).getPath(file,
-                locator.getSourceURL(), locator.getBasePath(), locator.getFileName());
+        if (locator.getBasePath() != null)
+        {
+            return locator.getBasePath();
+        }
+
+        if (locator.getSourceURL() != null)
+        {
+            return FileLocatorUtils.getBasePath(locator.getSourceURL());
+        }
+
+        return null;
     }
 
     /**
-     * Sets the location of the associated file as a full or relative path name.
-     * The passed in path should represent a valid file name on the file system.
-     * It must not be used to specify relative paths for files that exist in
-     * classpath, either plain file system or compressed archive, because this
-     * method expands any relative path to an absolute one which may end in an
-     * invalid absolute path for classpath references.
+     * Returns the {@code FileBased} object associated with this
+     * {@code FileHandler}.
      *
-     * @param path the full path name of the associated file
+     * @return the associated {@code FileBased} object
      */
-    public void setPath(final String path)
+    public final FileBased getContent()
     {
-        setFile(new File(path));
+        return content;
     }
 
     /**
-     * Returns the location of the associated file as a URL. If a URL is set,
-     * it is directly returned. Otherwise, an attempt to locate the referenced
-     * file is made.
+     * Returns the encoding of the associated file. Result can be <b>null</b> if
+     * no encoding has been set.
      *
-     * @return a URL to the associated file; can be <b>null</b> if the location
-     *         is unspecified
+     * @return the encoding of the associated file
      */
-    public URL getURL()
+    public String getEncoding()
     {
-        final FileLocator locator = getFileLocator();
-        return locator.getSourceURL() != null ? locator.getSourceURL()
-                : FileLocatorUtils.locate(locator);
+        return getFileLocator().getEncoding();
     }
 
     /**
-     * Sets the location of the associated file as a URL. For loading this can
-     * be an arbitrary URL with a supported protocol. If the file is to be
-     * saved, too, a URL with the &quot;file&quot; protocol should be provided.
-     * This method sets the file name and the base path to <b>null</b>.
-     * They have to be determined anew based on the new URL.
+     * Returns the location of the associated file as a {@code File} object. If
+     * the base path is a URL with a protocol different than &quot;file&quot;,
+     * or the file is within a compressed archive, the return value will not
+     * point to a valid file object.
      *
-     * @param url the location of the file as URL
+     * @return the location as {@code File} object; this can be <b>null</b>
      */
-    public void setURL(final URL url)
+    public File getFile()
     {
-        new Updater()
-        {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.sourceURL(url);
-                builder.basePath(null).fileName(null);
-            }
-        }
-        .update();
+        return createFile(getFileLocator());
     }
 
     /**
@@ -491,203 +586,132 @@ public class FileHandler
     }
 
     /**
-     * Sets the file to be accessed by this {@code FileHandler} as a
-     * {@code FileLocator} object.
+     * Return the name of the file. If only a URL is defined, the file name
+     * is derived from there.
      *
-     * @param locator the {@code FileLocator} with the definition of the file to
-     *        be accessed (must not be <b>null</b>
-     * @throws IllegalArgumentException if the {@code FileLocator} is
-     *         <b>null</b>
+     * @return the file name
      */
-    public void setFileLocator(final FileLocator locator)
+    public String getFileName()
     {
-        if (locator == null)
+        final FileLocator locator = getFileLocator();
+        if (locator.getFileName() != null)
         {
-            throw new IllegalArgumentException("FileLocator must not be null!");
+            return locator.getFileName();
         }
 
-        fileLocator.set(locator);
-        fireLocationChangedEvent();
+        if (locator.getSourceURL() != null)
+        {
+            return FileLocatorUtils.getFileName(locator.getSourceURL());
+        }
+
+        return null;
     }
 
     /**
-     * Tests whether a location is defined for this {@code FileHandler}.
+     * Returns the {@code FileSystem} to be used by this object when locating
+     * files. Result is never <b>null</b>; if no file system has been set, the
+     * default file system is returned.
      *
-     * @return <b>true</b> if a location is defined, <b>false</b> otherwise
+     * @return the used {@code FileSystem}
      */
-    public boolean isLocationDefined()
+    public FileSystem getFileSystem()
     {
-        return FileLocatorUtils.isLocationDefined(getFileLocator());
+        return FileLocatorUtils.obtainFileSystem(getFileLocator());
     }
 
     /**
-     * Clears the location of this {@code FileHandler}. Afterwards this handler
-     * does not point to any valid file.
+     * Returns the {@code FileLocationStrategy} to be applied when accessing the
+     * associated file. This method never returns <b>null</b>. If a
+     * {@code FileLocationStrategy} has been set, it is returned. Otherwise,
+     * result is the default {@code FileLocationStrategy}.
+     *
+     * @return the {@code FileLocationStrategy} to be used
      */
-    public void clearLocation()
+    public FileLocationStrategy getLocationStrategy()
     {
-        new Updater()
-        {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.basePath(null).fileName(null).sourceURL(null);
-            }
-        }
-        .update();
+        return FileLocatorUtils.obtainLocationStrategy(getFileLocator());
     }
 
     /**
-     * Returns the encoding of the associated file. Result can be <b>null</b> if
-     * no encoding has been set.
+     * Returns the full path to the associated file. The return value is a valid
+     * {@code File} path only if this location is based on a file on the local
+     * disk. If the file was loaded from a packed archive, the returned value is
+     * the string form of the URL from which the file was loaded.
      *
-     * @return the encoding of the associated file
+     * @return the full path to the associated file
      */
-    public String getEncoding()
+    public String getPath()
     {
-        return getFileLocator().getEncoding();
+        final FileLocator locator = getFileLocator();
+        final File file = createFile(locator);
+        return FileLocatorUtils.obtainFileSystem(locator).getPath(file,
+                locator.getSourceURL(), locator.getBasePath(), locator.getFileName());
     }
 
     /**
-     * Sets the encoding of the associated file. The encoding applies if binary
-     * files are loaded. Note that in this case setting an encoding is
-     * recommended; otherwise the platform's default encoding is used.
+     * Returns the location of the associated file as a URL. If a URL is set,
+     * it is directly returned. Otherwise, an attempt to locate the referenced
+     * file is made.
      *
-     * @param encoding the encoding of the associated file
+     * @return a URL to the associated file; can be <b>null</b> if the location
+     *         is unspecified
      */
-    public void setEncoding(final String encoding)
-    {
-        new Updater()
-        {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.encoding(encoding);
-            }
-        }
-        .update();
-    }
-
-    /**
-     * Returns the {@code FileSystem} to be used by this object when locating
-     * files. Result is never <b>null</b>; if no file system has been set, the
-     * default file system is returned.
-     *
-     * @return the used {@code FileSystem}
-     */
-    public FileSystem getFileSystem()
+    public URL getURL()
     {
-        return FileLocatorUtils.obtainFileSystem(getFileLocator());
+        final FileLocator locator = getFileLocator();
+        return locator.getSourceURL() != null ? locator.getSourceURL()
+                : FileLocatorUtils.locate(locator);
     }
 
     /**
-     * Sets the {@code FileSystem} to be used by this object when locating
-     * files. If a <b>null</b> value is passed in, the file system is reset to
-     * the default file system.
+     * Injects a {@code FileLocator} pointing to the specified URL if the
+     * current {@code FileBased} object implements the {@code FileLocatorAware}
+     * interface.
      *
-     * @param fileSystem the {@code FileSystem}
+     * @param url the URL for the locator
      */
-    public void setFileSystem(final FileSystem fileSystem)
+    private void injectFileLocator(final URL url)
     {
-        new Updater()
+        if (url == null)
         {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
+            injectNullFileLocator();
+        }
+        else
+        {
+            if (getContent() instanceof FileLocatorAware)
             {
-                builder.fileSystem(fileSystem);
+                final FileLocator locator =
+                        prepareNullLocatorBuilder().sourceURL(url).create();
+                ((FileLocatorAware) getContent()).initFileLocator(locator);
             }
         }
-        .update();
-    }
-
-    /**
-     * Resets the {@code FileSystem} used by this object. It is set to the
-     * default file system.
-     */
-    public void resetFileSystem()
-    {
-        setFileSystem(null);
-    }
-
-    /**
-     * Returns the {@code FileLocationStrategy} to be applied when accessing the
-     * associated file. This method never returns <b>null</b>. If a
-     * {@code FileLocationStrategy} has been set, it is returned. Otherwise,
-     * result is the default {@code FileLocationStrategy}.
-     *
-     * @return the {@code FileLocationStrategy} to be used
-     */
-    public FileLocationStrategy getLocationStrategy()
-    {
-        return FileLocatorUtils.obtainLocationStrategy(getFileLocator());
     }
 
     /**
-     * Sets the {@code FileLocationStrategy} to be applied when accessing the
-     * associated file. The strategy is stored in the underlying
-     * {@link FileLocator}. The argument can be <b>null</b>; this causes the
-     * default {@code FileLocationStrategy} to be used.
-     *
-     * @param strategy the {@code FileLocationStrategy}
-     * @see FileLocatorUtils#DEFAULT_LOCATION_STRATEGY
+     * Checks whether the associated {@code FileBased} object implements the
+     * {@code FileLocatorAware} interface. If this is the case, a
+     * {@code FileLocator} instance is injected which returns only <b>null</b>
+     * values. This method is called if no file location is available (e.g. if
+     * data is to be loaded from a stream). The encoding of the injected locator
+     * is derived from this object.
      */
-    public void setLocationStrategy(final FileLocationStrategy strategy)
+    private void injectNullFileLocator()
     {
-        new Updater()
+        if (getContent() instanceof FileLocatorAware)
         {
-            @Override
-            protected void updateBuilder(final FileLocatorBuilder builder)
-            {
-                builder.locationStrategy(strategy);
-            }
-
+            final FileLocator locator = prepareNullLocatorBuilder().create();
+            ((FileLocatorAware) getContent()).initFileLocator(locator);
         }
-        .update();
     }
 
     /**
-     * Locates the referenced file if necessary and ensures that the associated
-     * {@link FileLocator} is fully initialized. When accessing the referenced
-     * file the information stored in the associated {@code FileLocator} is
-     * used. If this information is incomplete (e.g. only the file name is set),
-     * an attempt to locate the file may have to be performed on each access. By
-     * calling this method such an attempt is performed once, and the results of
-     * a successful localization are stored. Hence, later access to the
-     * referenced file can be more efficient. Also, all properties pointing to
-     * the referenced file in this object's {@code FileLocator} are set (i.e.
-     * the URL, the base path, and the file name). If the referenced file cannot
-     * be located, result is <b>false</b>. This means that the information in
-     * the current {@code FileLocator} is insufficient or wrong. If the
-     * {@code FileLocator} is already fully defined, it is not changed.
+     * Tests whether a location is defined for this {@code FileHandler}.
      *
-     * @return a flag whether the referenced file could be located successfully
-     * @see FileLocatorUtils#fullyInitializedLocator(FileLocator)
+     * @return <b>true</b> if a location is defined, <b>false</b> otherwise
      */
-    public boolean locate()
+    public boolean isLocationDefined()
     {
-        boolean result;
-        boolean done;
-
-        do
-        {
-            final FileLocator locator = getFileLocator();
-            FileLocator fullLocator =
-                    FileLocatorUtils.fullyInitializedLocator(locator);
-            if (fullLocator == null)
-            {
-                result = false;
-                fullLocator = locator;
-            }
-            else
-            {
-                result =
-                        fullLocator != locator
-                                || FileLocatorUtils.isFullyInitialized(locator);
-            }
-            done = fileLocator.compareAndSet(locator, fullLocator);
-        } while (!done);
-
-        return result;
+        return FileLocatorUtils.isLocationDefined(getFileLocator());
     }
 
     /**
@@ -702,20 +726,6 @@ public class FileHandler
     }
 
     /**
-     * Loads the associated file from the given file name. The file name is
-     * interpreted in the context of the already set location (e.g. if it is a
-     * relative file name, a base path is applied if available). The underlying
-     * location is not changed.
-     *
-     * @param fileName the name of the file to be loaded
-     * @throws ConfigurationException if an error occurs
-     */
-    public void load(final String fileName) throws ConfigurationException
-    {
-        load(fileName, checkContentAndGetLocator());
-    }
-
-    /**
      * Loads the associated file from the specified {@code File}.
      *
      * @param file the file to load
@@ -738,15 +748,16 @@ public class FileHandler
     }
 
     /**
-     * Loads the associated file from the specified URL. The location stored in
-     * this object is not changed.
+     * Internal helper method for loading the associated file from the location
+     * specified in the given {@code FileLocator}.
      *
-     * @param url the URL of the file to be loaded
+     * @param locator the current {@code FileLocator}
      * @throws ConfigurationException if an error occurs
      */
-    public void load(final URL url) throws ConfigurationException
+    private void load(final FileLocator locator) throws ConfigurationException
     {
-        load(url, checkContentAndGetLocator());
+        final URL url = FileLocatorUtils.locateOrThrow(locator);
+        load(url, locator);
     }
 
     /**
@@ -763,6 +774,19 @@ public class FileHandler
     }
 
     /**
+     * Internal helper method for loading a file from the given input stream.
+     *
+     * @param in the input stream
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(final InputStream in, final FileLocator locator)
+            throws ConfigurationException
+    {
+        load(in, locator.getEncoding());
+    }
+
+    /**
      * Loads the associated file from the specified stream, using the specified
      * encoding. If the encoding is <b>null</b>, the default encoding is used.
      *
@@ -793,245 +817,101 @@ public class FileHandler
     }
 
     /**
-     * Saves the associated file to the current location set for this object.
-     * Before this method can be called a valid location must have been set.
+     * Loads the associated file from the given file name. The file name is
+     * interpreted in the context of the already set location (e.g. if it is a
+     * relative file name, a base path is applied if available). The underlying
+     * location is not changed.
      *
-     * @throws ConfigurationException if an error occurs or no location has been
-     *         set yet
+     * @param fileName the name of the file to be loaded
+     * @throws ConfigurationException if an error occurs
      */
-    public void save() throws ConfigurationException
+    public void load(final String fileName) throws ConfigurationException
     {
-        save(checkContentAndGetLocator());
+        load(fileName, checkContentAndGetLocator());
     }
 
     /**
-     * Saves the associated file to the specified file name. This does not
-     * change the location of this object (use {@link #setFileName(String)} if
-     * you need it).
+     * Internal helper method for loading a file from a file name.
      *
      * @param fileName the file name
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs
      */
-    public void save(final String fileName) throws ConfigurationException
+    private void load(final String fileName, final FileLocator locator)
+            throws ConfigurationException
     {
-        save(fileName, checkContentAndGetLocator());
+        final FileLocator locFileName = createLocatorWithFileName(fileName, locator);
+        final URL url = FileLocatorUtils.locateOrThrow(locFileName);
+        load(url, locator);
     }
 
     /**
-     * Saves the associated file to the specified URL. This does not change the
-     * location of this object (use {@link #setURL(URL)} if you need it).
+     * Loads the associated file from the specified URL. The location stored in
+     * this object is not changed.
      *
-     * @param url the URL
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
+     * @param url the URL of the file to be loaded
+     * @throws ConfigurationException if an error occurs
      */
-    public void save(final URL url) throws ConfigurationException
+    public void load(final URL url) throws ConfigurationException
     {
-        save(url, checkContentAndGetLocator());
+        load(url, checkContentAndGetLocator());
     }
 
     /**
-     * Saves the associated file to the specified {@code File}. The file is
-     * created automatically if it doesn't exist. This does not change the
-     * location of this object (use {@link #setFile} if you need it).
+     * Internal helper method for loading a file from the given URL.
      *
-     * @param file the target file
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
+     * @param url the URL
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs
      */
-    public void save(final File file) throws ConfigurationException
+    private void load(final URL url, final FileLocator locator) throws ConfigurationException
     {
-        save(file, checkContentAndGetLocator());
+        InputStream in = null;
+
+        try
+        {
+            in = FileLocatorUtils.obtainFileSystem(locator).getInputStream(url);
+            loadFromStream(in, locator.getEncoding(), url);
+        }
+        catch (final ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (final Exception e)
+        {
+            throw new ConfigurationException(
+                    "Unable to load the configuration from the URL " + url, e);
+        }
+        finally
+        {
+            closeSilent(in);
+        }
     }
 
     /**
-     * Saves the associated file to the specified stream using the encoding
-     * returned by {@link #getEncoding()}.
+     * Internal helper method for loading a file from the given reader.
      *
-     * @param out the output stream
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
-     */
-    public void save(final OutputStream out) throws ConfigurationException
-    {
-        save(out, checkContentAndGetLocator());
-    }
-
-    /**
-     * Saves the associated file to the specified stream using the specified
-     * encoding. If the encoding is <b>null</b>, the default encoding is used.
-     *
-     * @param out the output stream
-     * @param encoding the encoding to be used, {@code null} to use the default
-     *        encoding
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
-     */
-    public void save(final OutputStream out, final String encoding)
-            throws ConfigurationException
-    {
-        saveToStream(out, encoding, null);
-    }
-
-    /**
-     * Saves the associated file to the given {@code Writer}.
-     *
-     * @param out the {@code Writer}
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
-     */
-    public void save(final Writer out) throws ConfigurationException
-    {
-        checkContent();
-        injectNullFileLocator();
-        saveToWriter(out);
-    }
-
-    /**
-     * Prepares a builder for a {@code FileLocator} which does not have a
-     * defined file location. Other properties (e.g. encoding or file system)
-     * are initialized from the {@code FileLocator} associated with this object.
-     *
-     * @return the initialized builder for a {@code FileLocator}
-     */
-    private FileLocatorBuilder prepareNullLocatorBuilder()
-    {
-        return FileLocatorUtils.fileLocator(getFileLocator()).sourceURL(null)
-                .basePath(null).fileName(null);
-    }
-
-    /**
-     * Checks whether the associated {@code FileBased} object implements the
-     * {@code FileLocatorAware} interface. If this is the case, a
-     * {@code FileLocator} instance is injected which returns only <b>null</b>
-     * values. This method is called if no file location is available (e.g. if
-     * data is to be loaded from a stream). The encoding of the injected locator
-     * is derived from this object.
-     */
-    private void injectNullFileLocator()
-    {
-        if (getContent() instanceof FileLocatorAware)
-        {
-            final FileLocator locator = prepareNullLocatorBuilder().create();
-            ((FileLocatorAware) getContent()).initFileLocator(locator);
-        }
-    }
-
-    /**
-     * Injects a {@code FileLocator} pointing to the specified URL if the
-     * current {@code FileBased} object implements the {@code FileLocatorAware}
-     * interface.
-     *
-     * @param url the URL for the locator
-     */
-    private void injectFileLocator(final URL url)
-    {
-        if (url == null)
-        {
-            injectNullFileLocator();
-        }
-        else
-        {
-            if (getContent() instanceof FileLocatorAware)
-            {
-                final FileLocator locator =
-                        prepareNullLocatorBuilder().sourceURL(url).create();
-                ((FileLocatorAware) getContent()).initFileLocator(locator);
-            }
-        }
-    }
-
-    /**
-     * Obtains a {@code SynchronizerSupport} for the current content. If the
-     * content implements this interface, it is returned. Otherwise, result is a
-     * dummy object. This method is called before load and save operations. The
-     * returned object is used for synchronization.
-     *
-     * @return the {@code SynchronizerSupport} for synchronization
-     */
-    private SynchronizerSupport fetchSynchronizerSupport()
-    {
-        if (getContent() instanceof SynchronizerSupport)
-        {
-            return (SynchronizerSupport) getContent();
-        }
-        return DUMMY_SYNC_SUPPORT;
-    }
-
-    /**
-     * Internal helper method for loading the associated file from the location
-     * specified in the given {@code FileLocator}.
-     *
-     * @param locator the current {@code FileLocator}
-     * @throws ConfigurationException if an error occurs
-     */
-    private void load(final FileLocator locator) throws ConfigurationException
-    {
-        final URL url = FileLocatorUtils.locateOrThrow(locator);
-        load(url, locator);
-    }
-
-    /**
-     * Internal helper method for loading a file from the given URL.
-     *
-     * @param url the URL
-     * @param locator the current {@code FileLocator}
+     * @param in the reader
      * @throws ConfigurationException if an error occurs
      */
-    private void load(final URL url, final FileLocator locator) throws ConfigurationException
+    private void loadFromReader(final Reader in) throws ConfigurationException
     {
-        InputStream in = null;
-
+        fireLoadingEvent();
         try
         {
-            in = FileLocatorUtils.obtainFileSystem(locator).getInputStream(url);
-            loadFromStream(in, locator.getEncoding(), url);
-        }
-        catch (final ConfigurationException e)
-        {
-            throw e;
+            getContent().read(in);
         }
-        catch (final Exception e)
+        catch (final IOException ioex)
         {
-            throw new ConfigurationException(
-                    "Unable to load the configuration from the URL " + url, e);
+            throw new ConfigurationException(ioex);
         }
         finally
         {
-            closeSilent(in);
+            fireLoadedEvent();
         }
     }
 
     /**
-     * Internal helper method for loading a file from a file name.
-     *
-     * @param fileName the file name
-     * @param locator the current {@code FileLocator}
-     * @throws ConfigurationException if an error occurs
-     */
-    private void load(final String fileName, final FileLocator locator)
-            throws ConfigurationException
-    {
-        final FileLocator locFileName = createLocatorWithFileName(fileName, locator);
-        final URL url = FileLocatorUtils.locateOrThrow(locFileName);
-        load(url, locator);
-    }
-
-    /**
-     * Internal helper method for loading a file from the given input stream.
-     *
-     * @param in the input stream
-     * @param locator the current {@code FileLocator}
-     * @throws ConfigurationException if an error occurs
-     */
-    private void load(final InputStream in, final FileLocator locator)
-            throws ConfigurationException
-    {
-        load(in, locator.getEncoding());
-    }
-
-    /**
      * Internal helper method for loading a file from an input stream.
      *
      * @param in the input stream
@@ -1120,51 +1000,215 @@ public class FileHandler
     }
 
     /**
-     * Internal helper method for loading a file from the given reader.
+     * Locates the referenced file if necessary and ensures that the associated
+     * {@link FileLocator} is fully initialized. When accessing the referenced
+     * file the information stored in the associated {@code FileLocator} is
+     * used. If this information is incomplete (e.g. only the file name is set),
+     * an attempt to locate the file may have to be performed on each access. By
+     * calling this method such an attempt is performed once, and the results of
+     * a successful localization are stored. Hence, later access to the
+     * referenced file can be more efficient. Also, all properties pointing to
+     * the referenced file in this object's {@code FileLocator} are set (i.e.
+     * the URL, the base path, and the file name). If the referenced file cannot
+     * be located, result is <b>false</b>. This means that the information in
+     * the current {@code FileLocator} is insufficient or wrong. If the
+     * {@code FileLocator} is already fully defined, it is not changed.
      *
-     * @param in the reader
-     * @throws ConfigurationException if an error occurs
+     * @return a flag whether the referenced file could be located successfully
+     * @see FileLocatorUtils#fullyInitializedLocator(FileLocator)
      */
-    private void loadFromReader(final Reader in) throws ConfigurationException
+    public boolean locate()
     {
-        fireLoadingEvent();
-        try
-        {
-            getContent().read(in);
-        }
-        catch (final IOException ioex)
-        {
-            throw new ConfigurationException(ioex);
-        }
-        finally
+        boolean result;
+        boolean done;
+
+        do
         {
-            fireLoadedEvent();
-        }
+            final FileLocator locator = getFileLocator();
+            FileLocator fullLocator =
+                    FileLocatorUtils.fullyInitializedLocator(locator);
+            if (fullLocator == null)
+            {
+                result = false;
+                fullLocator = locator;
+            }
+            else
+            {
+                result =
+                        fullLocator != locator
+                                || FileLocatorUtils.isFullyInitialized(locator);
+            }
+            done = fileLocator.compareAndSet(locator, fullLocator);
+        } while (!done);
+
+        return result;
     }
 
     /**
-     * Internal helper method for saving data to the internal location stored
-     * for this object.
+     * Prepares a builder for a {@code FileLocator} which does not have a
+     * defined file location. Other properties (e.g. encoding or file system)
+     * are initialized from the {@code FileLocator} associated with this object.
      *
-     * @param locator the current {@code FileLocator}
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
+     * @return the initialized builder for a {@code FileLocator}
      */
-    private void save(final FileLocator locator) throws ConfigurationException
+    private FileLocatorBuilder prepareNullLocatorBuilder()
     {
-        if (!FileLocatorUtils.isLocationDefined(locator))
-        {
-            throw new ConfigurationException("No file location has been set!");
-        }
-
-        if (locator.getSourceURL() != null)
-        {
-            save(locator.getSourceURL(), locator);
-        }
-        else
-        {
-            save(locator.getFileName(), locator);
-        }
+        return FileLocatorUtils.fileLocator(getFileLocator()).sourceURL(null)
+                .basePath(null).fileName(null);
+    }
+
+    /**
+     * Removes the specified listener from this object.
+     *
+     * @param l the listener to be removed
+     */
+    public void removeFileHandlerListener(final FileHandlerListener l)
+    {
+        listeners.remove(l);
+    }
+
+    /**
+     * Resets the {@code FileSystem} used by this object. It is set to the
+     * default file system.
+     */
+    public void resetFileSystem()
+    {
+        setFileSystem(null);
+    }
+
+    /**
+     * Saves the associated file to the current location set for this object.
+     * Before this method can be called a valid location must have been set.
+     *
+     * @throws ConfigurationException if an error occurs or no location has been
+     *         set yet
+     */
+    public void save() throws ConfigurationException
+    {
+        save(checkContentAndGetLocator());
+    }
+
+    /**
+     * Saves the associated file to the specified {@code File}. The file is
+     * created automatically if it doesn't exist. This does not change the
+     * location of this object (use {@link #setFile} if you need it).
+     *
+     * @param file the target file
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    public void save(final File file) throws ConfigurationException
+    {
+        save(file, checkContentAndGetLocator());
+    }
+
+    /**
+     * Internal helper method for saving data to the given {@code File}.
+     *
+     * @param file the target file
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(final File file, final FileLocator locator) throws ConfigurationException
+    {
+        OutputStream out = null;
+
+        try
+        {
+            out = FileLocatorUtils.obtainFileSystem(locator).getOutputStream(file);
+            saveToStream(out, locator.getEncoding(), file.toURI().toURL());
+        }
+        catch (final MalformedURLException muex)
+        {
+            throw new ConfigurationException(muex);
+        }
+        finally
+        {
+            closeSilent(out);
+        }
+    }
+
+    /**
+     * Internal helper method for saving data to the internal location stored
+     * for this object.
+     *
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(final FileLocator locator) throws ConfigurationException
+    {
+        if (!FileLocatorUtils.isLocationDefined(locator))
+        {
+            throw new ConfigurationException("No file location has been set!");
+        }
+
+        if (locator.getSourceURL() != null)
+        {
+            save(locator.getSourceURL(), locator);
+        }
+        else
+        {
+            save(locator.getFileName(), locator);
+        }
+    }
+
+    /**
+     * Saves the associated file to the specified stream using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param out the output stream
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    public void save(final OutputStream out) throws ConfigurationException
+    {
+        save(out, checkContentAndGetLocator());
+    }
+
+    /**
+     * Internal helper method for saving a file to the given output stream.
+     *
+     * @param out the output stream
+     * @param locator the current {@code FileLocator}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(final OutputStream out, final FileLocator locator)
+            throws ConfigurationException
+    {
+        save(out, locator.getEncoding());
+    }
+
+    /**
+     * Saves the associated file to the specified stream using the specified
+     * encoding. If the encoding is <b>null</b>, the default encoding is used.
+     *
+     * @param out the output stream
+     * @param encoding the encoding to be used, {@code null} to use the default
+     *        encoding
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    public void save(final OutputStream out, final String encoding)
+            throws ConfigurationException
+    {
+        saveToStream(out, encoding, null);
+    }
+
+    /**
+     * Saves the associated file to the specified file name. This does not
+     * change the location of this object (use {@link #setFileName(String)} if
+     * you need it).
+     *
+     * @param fileName the file name
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    public void save(final String fileName) throws ConfigurationException
+    {
+        save(fileName, checkContentAndGetLocator());
     }
 
     /**
@@ -1198,6 +1242,19 @@ public class FileHandler
     }
 
     /**
+     * Saves the associated file to the specified URL. This does not change the
+     * location of this object (use {@link #setURL(URL)} if you need it).
+     *
+     * @param url the URL
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    public void save(final URL url) throws ConfigurationException
+    {
+        save(url, checkContentAndGetLocator());
+    }
+
+    /**
      * Internal helper method for saving data to the given URL.
      *
      * @param url the target URL
@@ -1231,44 +1288,17 @@ public class FileHandler
     }
 
     /**
-     * Internal helper method for saving data to the given {@code File}.
-     *
-     * @param file the target file
-     * @param locator the current {@code FileLocator}
-     * @throws ConfigurationException if an error occurs during the save
-     *         operation
-     */
-    private void save(final File file, final FileLocator locator) throws ConfigurationException
-    {
-        OutputStream out = null;
-
-        try
-        {
-            out = FileLocatorUtils.obtainFileSystem(locator).getOutputStream(file);
-            saveToStream(out, locator.getEncoding(), file.toURI().toURL());
-        }
-        catch (final MalformedURLException muex)
-        {
-            throw new ConfigurationException(muex);
-        }
-        finally
-        {
-            closeSilent(out);
-        }
-    }
-
-    /**
-     * Internal helper method for saving a file to the given output stream.
+     * Saves the associated file to the given {@code Writer}.
      *
-     * @param out the output stream
-     * @param locator the current {@code FileLocator}
+     * @param out the {@code Writer}
      * @throws ConfigurationException if an error occurs during the save
      *         operation
      */
-    private void save(final OutputStream out, final FileLocator locator)
-            throws ConfigurationException
+    public void save(final Writer out) throws ConfigurationException
     {
-        save(out, locator.getEncoding());
+        checkContent();
+        injectNullFileLocator();
+        saveToWriter(out);
     }
 
     /**
@@ -1341,233 +1371,203 @@ public class FileHandler
     }
 
     /**
-     * Creates a {@code FileLocator} which is a copy of the passed in one, but
-     * has the given file name set to reference the target file.
-     *
-     * @param fileName the file name
-     * @param locator the {@code FileLocator} to copy
-     * @return the manipulated {@code FileLocator} with the file name
-     */
-    private FileLocator createLocatorWithFileName(final String fileName,
-            final FileLocator locator)
-    {
-        return FileLocatorUtils.fileLocator(locator).sourceURL(null)
-                .fileName(fileName).create();
-    }
-
-    /**
-     * Checks whether a content object is available. If not, an exception is
-     * thrown. This method is called whenever the content object is accessed.
+     * Sets the base path. The base path is typically either a path to a
+     * directory or a URL. Together with the value passed to the
+     * {@code setFileName()} method it defines the location of the configuration
+     * file to be loaded. The strategies for locating the file are quite
+     * tolerant. For instance if the file name is already an absolute path or a
+     * fully defined URL, the base path will be ignored. The base path can also
+     * be a URL, in which case the file name is interpreted in this URL's
+     * context. If other methods are used for determining the location of the
+     * associated file (e.g. {@code setFile()} or {@code setURL()}), the base
+     * path is automatically set. Setting the base path using this method
+     * automatically sets the URL to <b>null</b> because it has to be
+     * determined anew based on the file name and the base path.
      *
-     * @throws ConfigurationException if not content object is defined
+     * @param basePath the base path.
      */
-    private void checkContent() throws ConfigurationException
+    public void setBasePath(final String basePath)
     {
-        if (getContent() == null)
+        final String path = normalizeFileURL(basePath);
+        new Updater()
         {
-            throw new ConfigurationException("No content available!");
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
+            {
+                builder.basePath(path);
+                builder.sourceURL(null);
+            }
         }
+        .update();
     }
 
     /**
-     * Checks whether a content object is available and returns the current
-     * {@code FileLocator}. If there is no content object, an exception is
-     * thrown. This is a typical operation to be performed before a load() or
-     * save() operation.
+     * Sets the encoding of the associated file. The encoding applies if binary
+     * files are loaded. Note that in this case setting an encoding is
+     * recommended; otherwise the platform's default encoding is used.
      *
-     * @return the current {@code FileLocator} to be used for the calling
-     *         operation
-     */
-    private FileLocator checkContentAndGetLocator()
-            throws ConfigurationException
-    {
-        checkContent();
-        return getFileLocator();
-    }
-
-    /**
-     * Notifies the registered listeners about the start of a load operation.
-     */
-    private void fireLoadingEvent()
-    {
-        for (final FileHandlerListener l : listeners)
-        {
-            l.loading(this);
-        }
-    }
-
-    /**
-     * Notifies the registered listeners about a completed load operation.
+     * @param encoding the encoding of the associated file
      */
-    private void fireLoadedEvent()
+    public void setEncoding(final String encoding)
     {
-        for (final FileHandlerListener l : listeners)
+        new Updater()
         {
-            l.loaded(this);
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
+            {
+                builder.encoding(encoding);
+            }
         }
+        .update();
     }
 
     /**
-     * Notifies the registered listeners about the start of a save operation.
+     * Sets the location of the associated file as a {@code File} object. The
+     * passed in {@code File} is made absolute if it is not yet. Then the file's
+     * path component becomes the base path and its name component becomes the
+     * file name.
+     *
+     * @param file the location of the associated file
      */
-    private void fireSavingEvent()
+    public void setFile(final File file)
     {
-        for (final FileHandlerListener l : listeners)
+        final String fileName = file.getName();
+        final String basePath =
+                file.getParentFile() != null ? file.getParentFile()
+                        .getAbsolutePath() : null;
+        new Updater()
         {
-            l.saving(this);
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
+            {
+                builder.fileName(fileName).basePath(basePath).sourceURL(null);
+            }
         }
+        .update();
     }
 
     /**
-     * Notifies the registered listeners about a completed save operation.
+     * Sets the file to be accessed by this {@code FileHandler} as a
+     * {@code FileLocator} object.
+     *
+     * @param locator the {@code FileLocator} with the definition of the file to
+     *        be accessed (must not be <b>null</b>
+     * @throws IllegalArgumentException if the {@code FileLocator} is
+     *         <b>null</b>
      */
-    private void fireSavedEvent()
+    public void setFileLocator(final FileLocator locator)
     {
-        for (final FileHandlerListener l : listeners)
+        if (locator == null)
         {
-            l.saved(this);
+            throw new IllegalArgumentException("FileLocator must not be null!");
         }
-    }
 
-    /**
-     * Notifies the registered listeners about a property update.
-     */
-    private void fireLocationChangedEvent()
-    {
-        for (final FileHandlerListener l : listeners)
-        {
-            l.locationChanged(this);
-        }
+        fileLocator.set(locator);
+        fireLocationChangedEvent();
     }
 
     /**
-     * Normalizes URLs to files. Ensures that file URLs start with the correct
-     * protocol.
+     * Set the name of the file. The passed in file name can contain a relative
+     * path. It must be used when referring files with relative paths from
+     * classpath. Use {@code setPath()} to set a full qualified file name. The
+     * URL is set to <b>null</b> as it has to be determined anew based on the
+     * file name and the base path.
      *
-     * @param fileName the string to be normalized
-     * @return the normalized file URL
+     * @param fileName the name of the file
      */
-    private static String normalizeFileURL(String fileName)
+    public void setFileName(final String fileName)
     {
-        if (fileName != null && fileName.startsWith(FILE_SCHEME)
-                && !fileName.startsWith(FILE_SCHEME_SLASH))
+        final String name = normalizeFileURL(fileName);
+        new Updater()
         {
-            fileName =
-                    FILE_SCHEME_SLASH
-                            + fileName.substring(FILE_SCHEME.length());
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
+            {
+                builder.fileName(name);
+                builder.sourceURL(null);
+            }
         }
-        return fileName;
+        .update();
     }
 
     /**
-     * A helper method for closing a stream. Occurring exceptions will be
-     * ignored.
+     * Sets the {@code FileSystem} to be used by this object when locating
+     * files. If a <b>null</b> value is passed in, the file system is reset to
+     * the default file system.
      *
-     * @param cl the stream to be closed (may be <b>null</b>)
+     * @param fileSystem the {@code FileSystem}
      */
-    private static void closeSilent(final Closeable cl)
+    public void setFileSystem(final FileSystem fileSystem)
     {
-        try
+        new Updater()
         {
-            if (cl != null)
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
             {
-                cl.close();
+                builder.fileSystem(fileSystem);
             }
         }
-        catch (final IOException e)
-        {
-            LogFactory.getLog(FileHandler.class).warn("Exception when closing " + cl, e);
-        }
+        .update();
     }
 
     /**
-     * Creates a {@code File} object from the content of the given
-     * {@code FileLocator} object. If the locator is not defined, result is
-     * <b>null</b>.
+     * Sets the {@code FileLocationStrategy} to be applied when accessing the
+     * associated file. The strategy is stored in the underlying
+     * {@link FileLocator}. The argument can be <b>null</b>; this causes the
+     * default {@code FileLocationStrategy} to be used.
      *
-     * @param loc the {@code FileLocator}
-     * @return a {@code File} object pointing to the associated file
+     * @param strategy the {@code FileLocationStrategy}
+     * @see FileLocatorUtils#DEFAULT_LOCATION_STRATEGY
      */
-    private static File createFile(final FileLocator loc)
+    public void setLocationStrategy(final FileLocationStrategy strategy)
     {
-        if (loc.getFileName() == null && loc.getSourceURL() == null)
-        {
-            return null;
-        }
-        else if (loc.getSourceURL() != null)
-        {
-            return FileLocatorUtils.fileFromURL(loc.getSourceURL());
-        }
-        else
+        new Updater()
         {
-            return FileLocatorUtils.getFile(loc.getBasePath(),
-                    loc.getFileName());
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
+            {
+                builder.locationStrategy(strategy);
+            }
+
         }
+        .update();
     }
 
     /**
-     * Creates an uninitialized file locator.
+     * Sets the location of the associated file as a full or relative path name.
+     * The passed in path should represent a valid file name on the file system.
+     * It must not be used to specify relative paths for files that exist in
+     * classpath, either plain file system or compressed archive, because this
+     * method expands any relative path to an absolute one which may end in an
+     * invalid absolute path for classpath references.
      *
-     * @return the locator
+     * @param path the full path name of the associated file
      */
-    private static FileLocator emptyFileLocator()
+    public void setPath(final String path)
     {
-        return FileLocatorUtils.fileLocator().create();
+        setFile(new File(path));
     }
 
     /**
-     * Helper method for checking a file handler which is to be copied. Throws
-     * an exception if the handler is <b>null</b>.
+     * Sets the location of the associated file as a URL. For loading this can
+     * be an arbitrary URL with a supported protocol. If the file is to be
+     * saved, too, a URL with the &quot;file&quot; protocol should be provided.
+     * This method sets the file name and the base path to <b>null</b>.
+     * They have to be determined anew based on the new URL.
      *
-     * @param c the {@code FileHandler} from which to copy the location
-     * @return the same {@code FileHandler}
-     */
-    private static FileHandler checkSourceHandler(final FileHandler c)
-    {
-        if (c == null)
-        {
-            throw new IllegalArgumentException(
-                    "FileHandler to assign must not be null!");
-        }
-        return c;
-    }
-
-    /**
-     * An internal class that performs all update operations of the handler's
-     * {@code FileLocator} in a safe way even if there is concurrent access.
-     * This class implements anon-blocking algorithm for replacing the immutable
-     * {@code FileLocator} instance stored in an atomic reference by a
-     * manipulated instance. (If we already had lambdas, this could be done
-     * without a class in a more elegant way.)
+     * @param url the location of the file as URL
      */
-    private abstract class Updater
+    public void setURL(final URL url)
     {
-        /**
-         * Performs an update of the enclosing file handler's
-         * {@code FileLocator} object.
-         */
-        public void update()
+        new Updater()
         {
-            boolean done;
-            do
+            @Override
+            protected void updateBuilder(final FileLocatorBuilder builder)
             {
-                final FileLocator oldLocator = fileLocator.get();
-                final FileLocatorBuilder builder =
-                        FileLocatorUtils.fileLocator(oldLocator);
-                updateBuilder(builder);
-                done = fileLocator.compareAndSet(oldLocator, builder.create());
-            } while (!done);
-            fireLocationChangedEvent();
+                builder.sourceURL(url);
+                builder.basePath(null).fileName(null);
+            }
         }
-
-        /**
-         * Updates the passed in builder object to apply the manipulation to be
-         * performed by this {@code Updater}. The builder has been setup with
-         * the former content of the {@code FileLocator} to be manipulated.
-         *
-         * @param builder the builder for creating an updated
-         *        {@code FileLocator}
-         */
-        protected abstract void updateBuilder(FileLocatorBuilder builder);
+        .update();
     }
 }


[commons-configuration] 05/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit bc15cb565eeaf2825b3ae9f7235a5680dc3f0f47
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:58:51 2020 -0400

    Sort members.
---
 .../configuration2/PropertiesConfiguration.java    | 2200 ++++++++++----------
 1 file changed, 1100 insertions(+), 1100 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
index e3d6967..c022ab8 100644
--- a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
@@ -206,518 +206,333 @@ public class PropertiesConfiguration extends BaseConfiguration
 {
 
     /**
-     * Defines default error handling for the special {@code "include"} key by throwing the given exception.
-     *
-     * @since 2.6
-     */
-    public static final ConfigurationConsumer<ConfigurationException> DEFAULT_INCLUDE_LISTENER = e -> { throw e; };
-
-    /**
-     * Defines error handling as a noop for the special {@code "include"} key.
-     *
-     * @since 2.6
-     */
-    public static final ConfigurationConsumer<ConfigurationException> NOOP_INCLUDE_LISTENER = e -> { /* noop */ };
-
-    /**
-     * The default encoding (ISO-8859-1 as specified by
-     * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
-     */
-    public static final String DEFAULT_ENCODING = "ISO-8859-1";
-
-    /** Constant for the supported comment characters.*/
-    static final String COMMENT_CHARS = "#!";
-
-    /** Constant for the default properties separator.*/
-    static final String DEFAULT_SEPARATOR = " = ";
-
-    /**
-     * A string with special characters that need to be unescaped when reading
-     * a properties file. {@code java.util.Properties} escapes these characters
-     * when writing out a properties file.
-     */
-    private static final String UNESCAPE_CHARACTERS = ":#=!\\\'\"";
-
-    /**
-     * This is the name of the property that can point to other
-     * properties file for including other properties files.
-     */
-    private static String include = "include";
-
-    /**
-     * This is the name of the property that can point to other
-     * properties file for including other properties files.
-     * <p>
-     * If the file is absent, processing continues normally.
-     * </p>
-     */
-    private static String includeOptional = "includeoptional";
-
-    /** The list of possible key/value separators */
-    private static final char[] SEPARATORS = new char[] {'=', ':'};
-
-    /** The white space characters used as key/value separators. */
-    private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
-
-    /** Constant for the platform specific line separator.*/
-    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
-    /** Constant for the radix of hex numbers.*/
-    private static final int HEX_RADIX = 16;
-
-    /** Constant for the length of a unicode literal.*/
-    private static final int UNICODE_LEN = 4;
-
-    /** Stores the layout object.*/
-    private PropertiesConfigurationLayout layout;
-
-    /** The include listener for the special {@code "include"} key. */
-    private ConfigurationConsumer<ConfigurationException> includeListener;
-
-    /** The IOFactory for creating readers and writers.*/
-    private IOFactory ioFactory;
-
-    /** The current {@code FileLocator}. */
-    private FileLocator locator;
-
-    /** Allow file inclusion or not */
-    private boolean includesAllowed = true;
-
-    /**
-     * Creates an empty PropertyConfiguration object which can be
-     * used to synthesize a new Properties file by adding values and
-     * then saving().
-     */
-    public PropertiesConfiguration()
-    {
-        installLayout(createLayout());
-    }
-
-    /**
-     * Gets the property value for including other properties files.
-     * By default it is "include".
-     *
-     * @return A String.
-     */
-    public static String getInclude()
-    {
-        return PropertiesConfiguration.include;
-    }
-
-    /**
-     * Gets the property value for including other properties files.
-     * By default it is "includeoptional".
      * <p>
-     * If the file is absent, processing continues normally.
+     * A default implementation of the {@code IOFactory} interface.
      * </p>
-     *
-     * @return A String.
-     * @since 2.5
-     */
-    public static String getIncludeOptional()
-    {
-        return PropertiesConfiguration.includeOptional;
-    }
-
-    /**
-     * Sets the property value for including other properties files.
-     * By default it is "include".
-     *
-     * @param inc A String.
-     */
-    public static void setInclude(final String inc)
-    {
-        PropertiesConfiguration.include = inc;
-    }
-
-    /**
-     * Sets the property value for including other properties files.
-     * By default it is "include".
      * <p>
-     * If the file is absent, processing continues normally.
+     * This class implements the {@code createXXXX()} methods defined by
+     * the {@code IOFactory} interface in a way that the default objects
+     * (i.e. {@code PropertiesReader} and {@code PropertiesWriter} are
+     * returned. Customizing either the reader or the writer (or both) can be
+     * done by extending this class and overriding the corresponding
+     * {@code createXXXX()} method.
      * </p>
      *
-     * @param inc A String.
-     * @since 2.5
-     */
-    public static void setIncludeOptional(final String inc)
-    {
-        PropertiesConfiguration.includeOptional = inc;
-    }
-
-    /**
-     * Controls whether additional files can be loaded by the {@code include = <xxx>}
-     * statement or not. This is <b>true</b> per default.
-     *
-     * @param includesAllowed True if Includes are allowed.
-     */
-    public void setIncludesAllowed(final boolean includesAllowed)
-    {
-        this.includesAllowed = includesAllowed;
-    }
-
-    /**
-     * Reports the status of file inclusion.
-     *
-     * @return True if include files are loaded.
+     * @since 1.7
      */
-    public boolean isIncludesAllowed()
+    public static class DefaultIOFactory implements IOFactory
     {
-        return this.includesAllowed;
-    }
+        /**
+         * The singleton instance.
+         */
+        static final DefaultIOFactory INSTANCE = new DefaultIOFactory();
 
-    /**
-     * Return the comment header.
-     *
-     * @return the comment header
-     * @since 1.1
-     */
-    public String getHeader()
-    {
-        beginRead(false);
-        try
-        {
-            return getLayout().getHeaderComment();
-        }
-        finally
+        @Override
+        public PropertiesReader createPropertiesReader(final Reader in)
         {
-            endRead();
+            return new PropertiesReader(in);
         }
-    }
 
-    /**
-     * Set the comment header.
-     *
-     * @param header the header to use
-     * @since 1.1
-     */
-    public void setHeader(final String header)
-    {
-        beginWrite(false);
-        try
-        {
-            getLayout().setHeaderComment(header);
-        }
-        finally
+        @Override
+        public PropertiesWriter createPropertiesWriter(final Writer out,
+                final ListDelimiterHandler handler)
         {
-            endWrite();
+            return new PropertiesWriter(out, handler);
         }
     }
 
     /**
-     * Returns the footer comment. This is a comment at the very end of the
-     * file.
+     * <p>
+     * Definition of an interface that allows customization of read and write
+     * operations.
+     * </p>
+     * <p>
+     * For reading and writing properties files the inner classes
+     * {@code PropertiesReader} and {@code PropertiesWriter} are used.
+     * This interface defines factory methods for creating both a
+     * {@code PropertiesReader} and a {@code PropertiesWriter}. An
+     * object implementing this interface can be passed to the
+     * {@code setIOFactory()} method of
+     * {@code PropertiesConfiguration}. Every time the configuration is
+     * read or written the {@code IOFactory} is asked to create the
+     * appropriate reader or writer object. This provides an opportunity to
+     * inject custom reader or writer implementations.
+     * </p>
      *
-     * @return the footer comment
-     * @since 2.0
+     * @since 1.7
      */
-    public String getFooter()
+    public interface IOFactory
     {
-        beginRead(false);
-        try
-        {
-            return getLayout().getFooterComment();
-        }
-        finally
-        {
-            endRead();
-        }
-    }
+        /**
+         * Creates a {@code PropertiesReader} for reading a properties
+         * file. This method is called whenever the
+         * {@code PropertiesConfiguration} is loaded. The reader returned
+         * by this method is then used for parsing the properties file.
+         *
+         * @param in the underlying reader (of the properties file)
+         * @return the {@code PropertiesReader} for loading the
+         *         configuration
+         */
+        PropertiesReader createPropertiesReader(Reader in);
 
-    /**
-     * Sets the footer comment. If set, this comment is written after all
-     * properties at the end of the file.
-     *
-     * @param footer the footer comment
-     * @since 2.0
-     */
-    public void setFooter(final String footer)
-    {
-        beginWrite(false);
-        try
-        {
-            getLayout().setFooterComment(footer);
-        }
-        finally
-        {
-            endWrite();
-        }
+        /**
+         * Creates a {@code PropertiesWriter} for writing a properties
+         * file. This method is called before the
+         * {@code PropertiesConfiguration} is saved. The writer returned by
+         * this method is then used for writing the properties file.
+         *
+         * @param out the underlying writer (to the properties file)
+         * @param handler the list delimiter delimiter for list parsing
+         * @return the {@code PropertiesWriter} for saving the
+         *         configuration
+         */
+        PropertiesWriter createPropertiesWriter(Writer out,
+                ListDelimiterHandler handler);
     }
 
     /**
-     * Returns the associated layout object.
+     * An alternative {@link IOFactory} that tries to mimic the behavior of
+     * {@link java.util.Properties} (Jup) more closely. The goal is to allow both of
+     * them be used interchangeably when reading and writing properties files
+     * without losing or changing information.
+     * <p>
+     * It also has the option to <em>not</em> use Unicode escapes. When using UTF-8
+     * encoding (which is e.g. the new default for resource bundle properties files
+     * since Java 9), Unicode escapes are no longer required and avoiding them makes
+     * properties files more readable with regular text editors.
+     * <p>
+     * Some of the ways this implementation differs from {@link DefaultIOFactory}:
+     * <ul>
+     * <li>Trailing whitespace will not be trimmed from each line.</li>
+     * <li>Unknown escape sequences will have their backslash removed.</li>
+     * <li>{@code \b} is not a recognized escape sequence.</li>
+     * <li>Leading spaces in property values are preserved by escaping them.</li>
+     * <li>All natural lines (i.e. in the file) of a logical property line will have
+     * their leading whitespace trimmed.</li>
+     * <li>Natural lines that look like comment lines within a logical line are not
+     * treated as such; they're part of the property value.</li>
+     * </ul>
      *
-     * @return the associated layout object
-     * @since 1.3
+     * @since 2.4
      */
-    public PropertiesConfigurationLayout getLayout()
+    public static class JupIOFactory implements IOFactory
     {
-        return layout;
-    }
 
-    /**
-     * Sets the associated layout object.
-     *
-     * @param layout the new layout object; can be <b>null</b>, then a new
-     * layout object will be created
-     * @since 1.3
-     */
-    public void setLayout(final PropertiesConfigurationLayout layout)
-    {
-        installLayout(layout);
-    }
+        /**
+         * Whether characters less than {@code \u0020} and characters greater than
+         * {@code \u007E} in property keys or values should be escaped using
+         * Unicode escape sequences. Not necessary when e.g. writing as UTF-8.
+         */
+        private final boolean escapeUnicode;
 
-    /**
-     * Installs a layout object. It has to be ensured that the layout is
-     * registered as change listener at this configuration. If there is already
-     * a layout object installed, it has to be removed properly.
-     *
-     * @param layout the layout object to be installed
-     */
-    private void installLayout(final PropertiesConfigurationLayout layout)
-    {
-        // only one layout must exist
-        if (this.layout != null)
+        /**
+         * Constructs a new {@link JupIOFactory} with Unicode escaping.
+         */
+        public JupIOFactory()
         {
-            removeEventListener(ConfigurationEvent.ANY, this.layout);
+            this(true);
         }
 
-        if (layout == null)
-        {
-            this.layout = createLayout();
-        }
-        else
+        /**
+         * Constructs a new {@link JupIOFactory} with optional Unicode escaping. Whether
+         * Unicode escaping is required depends on the encoding used to save the
+         * properties file. E.g. for ISO-8859-1 this must be turned on, for UTF-8 it's
+         * not necessary. Unfortunately this factory can't determine the encoding on its
+         * own.
+         *
+         * @param escapeUnicode whether Unicode characters should be escaped
+         */
+        public JupIOFactory(final boolean escapeUnicode)
         {
-            this.layout = layout;
+            this.escapeUnicode = escapeUnicode;
         }
-        addEventListener(ConfigurationEvent.ANY, this.layout);
-    }
-
-    /**
-     * Creates a standard layout object. This configuration is initialized with
-     * such a standard layout.
-     *
-     * @return the newly created layout object
-     */
-    private PropertiesConfigurationLayout createLayout()
-    {
-        return new PropertiesConfigurationLayout();
-    }
 
-    /**
-     * Gets the current include listener, never null.
-     *
-     * @return the current include listener, never null.
-     * @since 2.6
-     */
-    public ConfigurationConsumer<ConfigurationException> getIncludeListener()
-    {
-        return includeListener != null ? includeListener : PropertiesConfiguration.DEFAULT_INCLUDE_LISTENER;
-    }
-
-    /**
-     * Returns the {@code IOFactory} to be used for creating readers and
-     * writers when loading or saving this configuration.
-     *
-     * @return the {@code IOFactory}
-     * @since 1.7
-     */
-    public IOFactory getIOFactory()
-    {
-        return ioFactory != null ? ioFactory : DefaultIOFactory.INSTANCE;
-    }
-
-    /**
-     * Sets the current include listener, may not be null.
-     *
-     * @param includeListener the current include listener, may not be null.
-     * @throws IllegalArgumentException if the {@code includeListener} is null.
-     * @since 2.6
-     */
-    public void setIncludeListener(final ConfigurationConsumer<ConfigurationException> includeListener)
-    {
-        if (includeListener == null)
+        @Override
+        public PropertiesReader createPropertiesReader(final Reader in)
         {
-            throw new IllegalArgumentException("includeListener must not be null.");
+            return new JupPropertiesReader(in);
         }
-        this.includeListener = includeListener;
-    }
 
-    /**
-     * Sets the {@code IOFactory} to be used for creating readers and
-     * writers when loading or saving this configuration. Using this method a
-     * client can customize the reader and writer classes used by the load and
-     * save operations. Note that this method must be called before invoking
-     * one of the {@code load()} and {@code save()} methods.
-     * Especially, if you want to use a custom {@code IOFactory} for
-     * changing the {@code PropertiesReader}, you cannot load the
-     * configuration data in the constructor.
-     *
-     * @param ioFactory the new {@code IOFactory} (must not be <b>null</b>)
-     * @throws IllegalArgumentException if the {@code IOFactory} is
-     *         <b>null</b>
-     * @since 1.7
-     */
-    public void setIOFactory(final IOFactory ioFactory)
-    {
-        if (ioFactory == null)
+        @Override
+        public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler)
         {
-            throw new IllegalArgumentException("IOFactory must not be null.");
+            return new JupPropertiesWriter(out, handler, escapeUnicode);
         }
 
-        this.ioFactory = ioFactory;
     }
 
     /**
-     * Stores the current {@code FileLocator} for a following IO operation. The
-     * {@code FileLocator} is needed to resolve include files with relative file
-     * names.
+     * A {@link PropertiesReader} that tries to mimic the behavior of
+     * {@link java.util.Properties}.
      *
-     * @param locator the current {@code FileLocator}
-     * @since 2.0
+     * @since 2.4
      */
-    @Override
-    public void initFileLocator(final FileLocator locator)
+    public static class JupPropertiesReader extends PropertiesReader
     {
-        this.locator = locator;
-    }
 
-    /**
-     * {@inheritDoc} This implementation delegates to the associated layout
-     * object which does the actual loading. Note that this method does not
-     * do any synchronization. This lies in the responsibility of the caller.
-     * (Typically, the caller is a {@code FileHandler} object which takes
-     * care for proper synchronization.)
-     *
-     * @since 2.0
-     */
-    @Override
-    public void read(final Reader in) throws ConfigurationException, IOException
-    {
-        getLayout().load(this, in);
-    }
+        /**
+         * Constructor.
+         *
+         * @param reader A Reader.
+         */
+        public JupPropertiesReader(final Reader reader)
+        {
+            super(reader);
+        }
 
-    /**
-     * {@inheritDoc} This implementation delegates to the associated layout
-     * object which does the actual saving. Note that, analogous to
-     * {@link #read(Reader)}, this method does not do any synchronization.
-     *
-     * @since 2.0
-     */
-    @Override
-    public void write(final Writer out) throws ConfigurationException, IOException
-    {
-        getLayout().save(this, out);
-    }
 
-    /**
-     * Creates a copy of this object.
-     *
-     * @return the copy
-     */
-    @Override
-    public Object clone()
-    {
-        final PropertiesConfiguration copy = (PropertiesConfiguration) super.clone();
-        if (layout != null)
+        @Override
+        protected void parseProperty(final String line)
         {
-            copy.setLayout(new PropertiesConfigurationLayout(layout));
+            final String[] property = doParseProperty(line, false);
+            initPropertyName(property[0]);
+            initPropertyValue(property[1]);
+            initPropertySeparator(property[2]);
         }
-        return copy;
-    }
-
-    /**
-     * This method is invoked by the associated
-     * {@link PropertiesConfigurationLayout} object for each
-     * property definition detected in the parsed properties file. Its task is
-     * to check whether this is a special property definition (e.g. the
-     * {@code include} property). If not, the property must be added to
-     * this configuration. The return value indicates whether the property
-     * should be treated as a normal property. If it is <b>false</b>, the
-     * layout object will ignore this property.
-     *
-     * @param key the property key
-     * @param value the property value
-     * @param seenStack the stack of seen include URLs
-     * @return a flag whether this is a normal property
-     * @throws ConfigurationException if an error occurs
-     * @since 1.3
-     */
-    boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack)
-            throws ConfigurationException
-    {
-        boolean result;
 
-        if (StringUtils.isNotEmpty(getInclude())
-                && key.equalsIgnoreCase(getInclude()))
+        @Override
+        public String readProperty() throws IOException
         {
-            if (isIncludesAllowed())
+            getCommentLines().clear();
+            final StringBuilder buffer = new StringBuilder();
+
+            while (true)
             {
-                final Collection<String> files =
-                        getListDelimiterHandler().split(value, true);
-                for (final String f : files)
+                String line = readLine();
+                if (line == null)
                 {
-                    loadIncludeFile(interpolate(f), false, seenStack);
+                    // EOF
+                    if (buffer.length() > 0)
+                    {
+                        break;
+                    }
+                    return null;
                 }
-            }
-            result = false;
-        }
 
-        else if (StringUtils.isNotEmpty(getIncludeOptional())
-            && key.equalsIgnoreCase(getIncludeOptional()))
-        {
-            if (isIncludesAllowed())
-            {
-                final Collection<String> files =
-                        getListDelimiterHandler().split(value, true);
-                for (final String f : files)
+                // while a property line continues there are no comments (even if the line from
+                // the file looks like one)
+                if (isCommentLine(line) && (buffer.length() == 0))
                 {
-                    loadIncludeFile(interpolate(f), true, seenStack);
+                    getCommentLines().add(line);
+                    continue;
+                }
+
+                // while property line continues left trim all following lines read from the
+                // file
+                if (buffer.length() > 0)
+                {
+                    // index of the first non-whitespace character
+                    int i;
+                    for (i = 0; i < line.length(); i++)
+                    {
+                        if (!Character.isWhitespace(line.charAt(i)))
+                        {
+                            break;
+                        }
+                    }
+
+                    line = line.substring(i);
+                }
+
+                if (checkCombineLines(line))
+                {
+                    line = line.substring(0, line.length() - 1);
+                    buffer.append(line);
+                }
+                else
+                {
+                    buffer.append(line);
+                    break;
                 }
             }
-            result = false;
+            return buffer.toString();
         }
 
-        else
+        @Override
+        protected String unescapePropertyValue(final String value)
         {
-            addPropertyInternal(key, value);
-            result = true;
+            return unescapeJava(value, true);
         }
 
-        return result;
     }
 
-    /**
-     * Tests whether a line is a comment, i.e. whether it starts with a comment
-     * character.
-     *
-     * @param line the line
-     * @return a flag if this is a comment line
-     * @since 1.3
-     */
-    static boolean isCommentLine(final String line)
-    {
-        final String s = line.trim();
-        // blanc lines are also treated as comment lines
-        return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
-    }
+    /**
+     * A {@link PropertiesWriter} that tries to mimic the behavior of
+     * {@link java.util.Properties}.
+     *
+     * @since 2.4
+     */
+    public static class JupPropertiesWriter extends PropertiesWriter
+    {
+
+        /**
+         * The starting ASCII printable character.
+         */
+        private static final int PRINTABLE_INDEX_END = 0x7e;
+
+        /**
+         * The ending ASCII printable character.
+         */
+        private static final int PRINTABLE_INDEX_START = 0x20;
+
+        /**
+         * A UnicodeEscaper for characters outside the ASCII printable range.
+         */
+        private static final UnicodeEscaper ESCAPER = UnicodeEscaper.outsideOf(PRINTABLE_INDEX_START,
+            PRINTABLE_INDEX_END);
+
+        /**
+         * Characters that need to be escaped when wring a properties file.
+         */
+        private static final Map<CharSequence, CharSequence> JUP_CHARS_ESCAPE;
+        static
+        {
+            final Map<CharSequence, CharSequence> initialMap = new HashMap<>();
+            initialMap.put("\\", "\\\\");
+            initialMap.put("\n", "\\n");
+            initialMap.put("\t", "\\t");
+            initialMap.put("\f", "\\f");
+            initialMap.put("\r", "\\r");
+            JUP_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap);
+        }
+
+        /**
+         * Creates a new instance of {@code JupPropertiesWriter}.
+         *
+         * @param writer a Writer object providing the underlying stream
+         * @param delHandler the delimiter handler for dealing with properties with
+         *        multiple values
+         * @param escapeUnicode whether Unicode characters should be escaped using
+         *        Unicode escapes
+         */
+        public JupPropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler,
+            final boolean escapeUnicode)
+        {
+            super(writer, delHandler, value -> {
+                String valueString = String.valueOf(value);
+
+                CharSequenceTranslator translator;
+                if (escapeUnicode)
+                {
+                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE), ESCAPER);
+                }
+                else
+                {
+                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE));
+                }
+
+                valueString = translator.translate(valueString);
+
+                // escape the first leading space to preserve it (and all after it)
+                if (valueString.startsWith(" "))
+                {
+                    valueString = "\\" + valueString;
+                }
 
-    /**
-     * Returns the number of trailing backslashes. This is sometimes needed for
-     * the correct handling of escape characters.
-     *
-     * @param line the string to investigate
-     * @return the number of trailing backslashes
-     */
-    private static int countTrailingBS(final String line)
-    {
-        int bsCount = 0;
-        for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
-        {
-            bsCount++;
+                return valueString;
+            });
         }
 
-        return bsCount;
     }
 
     /**
@@ -743,99 +558,70 @@ public class PropertiesConfiguration extends BaseConfiguration
         /** Constant for the index of the group for the separator. */
         private static final int IDX_SEPARATOR = 3;
 
-        /** Stores the comment lines for the currently processed property.*/
-        private final List<String> commentLines;
-
-        /** Stores the name of the last read property.*/
-        private String propertyName;
-
-        /** Stores the value of the last read property.*/
-        private String propertyValue;
-
-        /** Stores the property separator of the last read property.*/
-        private String propertySeparator = DEFAULT_SEPARATOR;
-
         /**
-         * Constructor.
+         * Checks if the passed in line should be combined with the following.
+         * This is true, if the line ends with an odd number of backslashes.
          *
-         * @param reader A Reader.
+         * @param line the line
+         * @return a flag if the lines should be combined
          */
-        public PropertiesReader(final Reader reader)
+        static boolean checkCombineLines(final String line)
         {
-            super(reader);
-            commentLines = new ArrayList<>();
+            return countTrailingBS(line) % 2 != 0;
         }
 
         /**
-         * Reads a property line. Returns null if Stream is
-         * at EOF. Concatenates lines ending with "\".
-         * Skips lines beginning with "#" or "!" and empty lines.
-         * The return value is a property definition ({@code &lt;name&gt;}
-         * = {@code &lt;value&gt;})
-         *
-         * @return A string containing a property value or null
+         * Parse a property line and return the key, the value, and the separator in an
+         * array.
          *
-         * @throws IOException in case of an I/O error
+         * @param line the line to parse
+         * @param trimValue flag whether the value is to be trimmed
+         * @return an array with the property's key, value, and separator
          */
-        public String readProperty() throws IOException
+        static String[] doParseProperty(final String line, final boolean trimValue)
         {
-            commentLines.clear();
-            final StringBuilder buffer = new StringBuilder();
+            final Matcher matcher = PROPERTY_PATTERN.matcher(line);
 
-            while (true)
+            final String[] result = {"", "", ""};
+
+            if (matcher.matches())
             {
-                String line = readLine();
-                if (line == null)
-                {
-                    // EOF
-                    return null;
-                }
+                result[0] = matcher.group(IDX_KEY).trim();
 
-                if (isCommentLine(line))
+                String value = matcher.group(IDX_VALUE);
+                if (trimValue)
                 {
-                    commentLines.add(line);
-                    continue;
+                    value = value.trim();
                 }
+                result[1] = value;
 
-                line = line.trim();
-
-                if (checkCombineLines(line))
-                {
-                    line = line.substring(0, line.length() - 1);
-                    buffer.append(line);
-                }
-                else
-                {
-                    buffer.append(line);
-                    break;
-                }
+                result[2] = matcher.group(IDX_SEPARATOR);
             }
-            return buffer.toString();
+
+            return result;
         }
 
+        /** Stores the comment lines for the currently processed property.*/
+        private final List<String> commentLines;
+
+        /** Stores the name of the last read property.*/
+        private String propertyName;
+
+        /** Stores the value of the last read property.*/
+        private String propertyValue;
+
+        /** Stores the property separator of the last read property.*/
+        private String propertySeparator = DEFAULT_SEPARATOR;
+
         /**
-         * Parses the next property from the input stream and stores the found
-         * name and value in internal fields. These fields can be obtained using
-         * the provided getter methods. The return value indicates whether EOF
-         * was reached (<b>false</b>) or whether further properties are
-         * available (<b>true</b>).
+         * Constructor.
          *
-         * @return a flag if further properties are available
-         * @throws IOException if an error occurs
-         * @since 1.3
+         * @param reader A Reader.
          */
-        public boolean nextProperty() throws IOException
+        public PropertiesReader(final Reader reader)
         {
-            final String line = readProperty();
-
-            if (line == null)
-            {
-                return false; // EOF
-            }
-
-            // parse the line
-            parseProperty(line);
-            return true;
+            super(reader);
+            commentLines = new ArrayList<>();
         }
 
         /**
@@ -864,6 +650,19 @@ public class PropertiesConfiguration extends BaseConfiguration
         }
 
         /**
+         * Returns the separator that was used for the last read property. The
+         * separator can be stored so that it can later be restored when saving
+         * the configuration.
+         *
+         * @return the separator for the last read property
+         * @since 1.7
+         */
+        public String getPropertySeparator()
+        {
+            return propertySeparator;
+        }
+
+        /**
          * Returns the value of the last read property. This method can be
          * called after {@link #nextProperty()} was invoked and
          * its return value was <b>true</b>.
@@ -877,16 +676,70 @@ public class PropertiesConfiguration extends BaseConfiguration
         }
 
         /**
-         * Returns the separator that was used for the last read property. The
-         * separator can be stored so that it can later be restored when saving
-         * the configuration.
+         * Sets the name of the current property. This method can be called by
+         * {@code parseProperty()} for storing the results of the parse
+         * operation. It also ensures that the property key is correctly
+         * escaped.
          *
-         * @return the separator for the last read property
+         * @param name the name of the current property
          * @since 1.7
          */
-        public String getPropertySeparator()
+        protected void initPropertyName(final String name)
         {
-            return propertySeparator;
+            propertyName = unescapePropertyName(name);
+        }
+
+        /**
+         * Sets the separator of the current property. This method can be called
+         * by {@code parseProperty()}. It allows the associated layout
+         * object to keep track of the property separators. When saving the
+         * configuration the separators can be restored.
+         *
+         * @param value the separator used for the current property
+         * @since 1.7
+         */
+        protected void initPropertySeparator(final String value)
+        {
+            propertySeparator = value;
+        }
+
+        /**
+         * Sets the value of the current property. This method can be called by
+         * {@code parseProperty()} for storing the results of the parse
+         * operation. It also ensures that the property value is correctly
+         * escaped.
+         *
+         * @param value the value of the current property
+         * @since 1.7
+         */
+        protected void initPropertyValue(final String value)
+        {
+            propertyValue = unescapePropertyValue(value);
+        }
+
+        /**
+         * Parses the next property from the input stream and stores the found
+         * name and value in internal fields. These fields can be obtained using
+         * the provided getter methods. The return value indicates whether EOF
+         * was reached (<b>false</b>) or whether further properties are
+         * available (<b>true</b>).
+         *
+         * @return a flag if further properties are available
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public boolean nextProperty() throws IOException
+        {
+            final String line = readProperty();
+
+            if (line == null)
+            {
+                return false; // EOF
+            }
+
+            // parse the line
+            parseProperty(line);
+            return true;
         }
 
         /**
@@ -908,17 +761,50 @@ public class PropertiesConfiguration extends BaseConfiguration
         }
 
         /**
-         * Sets the name of the current property. This method can be called by
-         * {@code parseProperty()} for storing the results of the parse
-         * operation. It also ensures that the property key is correctly
-         * escaped.
+         * Reads a property line. Returns null if Stream is
+         * at EOF. Concatenates lines ending with "\".
+         * Skips lines beginning with "#" or "!" and empty lines.
+         * The return value is a property definition ({@code &lt;name&gt;}
+         * = {@code &lt;value&gt;})
          *
-         * @param name the name of the current property
-         * @since 1.7
+         * @return A string containing a property value or null
+         *
+         * @throws IOException in case of an I/O error
          */
-        protected void initPropertyName(final String name)
+        public String readProperty() throws IOException
         {
-            propertyName = unescapePropertyName(name);
+            commentLines.clear();
+            final StringBuilder buffer = new StringBuilder();
+
+            while (true)
+            {
+                String line = readLine();
+                if (line == null)
+                {
+                    // EOF
+                    return null;
+                }
+
+                if (isCommentLine(line))
+                {
+                    commentLines.add(line);
+                    continue;
+                }
+
+                line = line.trim();
+
+                if (checkCombineLines(line))
+                {
+                    line = line.substring(0, line.length() - 1);
+                    buffer.append(line);
+                }
+                else
+                {
+                    buffer.append(line);
+                    break;
+                }
+            }
+            return buffer.toString();
         }
 
         /**
@@ -934,20 +820,6 @@ public class PropertiesConfiguration extends BaseConfiguration
         }
 
         /**
-         * Sets the value of the current property. This method can be called by
-         * {@code parseProperty()} for storing the results of the parse
-         * operation. It also ensures that the property value is correctly
-         * escaped.
-         *
-         * @param value the value of the current property
-         * @since 1.7
-         */
-        protected void initPropertyValue(final String value)
-        {
-            propertyValue = unescapePropertyValue(value);
-        }
-
-        /**
          * Performs unescaping on the given property value.
          *
          * @param value the property value
@@ -958,63 +830,6 @@ public class PropertiesConfiguration extends BaseConfiguration
         {
             return unescapeJava(value);
         }
-
-        /**
-         * Sets the separator of the current property. This method can be called
-         * by {@code parseProperty()}. It allows the associated layout
-         * object to keep track of the property separators. When saving the
-         * configuration the separators can be restored.
-         *
-         * @param value the separator used for the current property
-         * @since 1.7
-         */
-        protected void initPropertySeparator(final String value)
-        {
-            propertySeparator = value;
-        }
-
-        /**
-         * Checks if the passed in line should be combined with the following.
-         * This is true, if the line ends with an odd number of backslashes.
-         *
-         * @param line the line
-         * @return a flag if the lines should be combined
-         */
-        static boolean checkCombineLines(final String line)
-        {
-            return countTrailingBS(line) % 2 != 0;
-        }
-
-        /**
-         * Parse a property line and return the key, the value, and the separator in an
-         * array.
-         *
-         * @param line the line to parse
-         * @param trimValue flag whether the value is to be trimmed
-         * @return an array with the property's key, value, and separator
-         */
-        static String[] doParseProperty(final String line, final boolean trimValue)
-        {
-            final Matcher matcher = PROPERTY_PATTERN.matcher(line);
-
-            final String[] result = {"", "", ""};
-
-            if (matcher.matches())
-            {
-                result[0] = matcher.group(IDX_KEY).trim();
-
-                String value = matcher.group(IDX_VALUE);
-                if (trimValue)
-                {
-                    value = value.trim();
-                }
-                result[1] = value;
-
-                result[2] = matcher.group(IDX_SEPARATOR);
-            }
-
-            return result;
-        }
     } // class PropertiesReader
 
     /**
@@ -1065,218 +880,40 @@ public class PropertiesConfiguration extends BaseConfiguration
         private final ListDelimiterHandler delimiterHandler;
 
         /** The separator to be used for the current property. */
-        private String currentSeparator;
-
-        /** The global separator. If set, it overrides the current separator.*/
-        private String globalSeparator;
-
-        /** The line separator.*/
-        private String lineSeparator;
-
-        /**
-         * Creates a new instance of {@code PropertiesWriter}.
-         *
-         * @param writer a Writer object providing the underlying stream
-         * @param delHandler the delimiter handler for dealing with properties
-         *        with multiple values
-         */
-        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler)
-        {
-            this(writer, delHandler, DEFAULT_TRANSFORMER);
-        }
-
-        /**
-         * Creates a new instance of {@code PropertiesWriter}.
-         *
-         * @param writer a Writer object providing the underlying stream
-         * @param delHandler the delimiter handler for dealing with properties
-         *        with multiple values
-         * @param valueTransformer the value transformer used to escape property values
-         */
-        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler,
-            final ValueTransformer valueTransformer)
-        {
-            super(writer);
-            delimiterHandler = delHandler;
-            this.valueTransformer = valueTransformer;
-        }
-
-        /**
-         * Returns the delimiter handler for properties with multiple values.
-         * This object is used to escape property values so that they can be
-         * read in correctly the next time they are loaded.
-         *
-         * @return the delimiter handler for properties with multiple values
-         * @since 2.0
-         */
-        public ListDelimiterHandler getDelimiterHandler()
-        {
-            return delimiterHandler;
-        }
-
-        /**
-         * Returns the current property separator.
-         *
-         * @return the current property separator
-         * @since 1.7
-         */
-        public String getCurrentSeparator()
-        {
-            return currentSeparator;
-        }
-
-        /**
-         * Sets the current property separator. This separator is used when
-         * writing the next property.
-         *
-         * @param currentSeparator the current property separator
-         * @since 1.7
-         */
-        public void setCurrentSeparator(final String currentSeparator)
-        {
-            this.currentSeparator = currentSeparator;
-        }
-
-        /**
-         * Returns the global property separator.
-         *
-         * @return the global property separator
-         * @since 1.7
-         */
-        public String getGlobalSeparator()
-        {
-            return globalSeparator;
-        }
-
-        /**
-         * Sets the global property separator. This separator corresponds to the
-         * {@code globalSeparator} property of
-         * {@link PropertiesConfigurationLayout}. It defines the separator to be
-         * used for all properties. If it is undefined, the current separator is
-         * used.
-         *
-         * @param globalSeparator the global property separator
-         * @since 1.7
-         */
-        public void setGlobalSeparator(final String globalSeparator)
-        {
-            this.globalSeparator = globalSeparator;
-        }
-
-        /**
-         * Returns the line separator.
-         *
-         * @return the line separator
-         * @since 1.7
-         */
-        public String getLineSeparator()
-        {
-            return lineSeparator != null ? lineSeparator : LINE_SEPARATOR;
-        }
-
-        /**
-         * Sets the line separator. Each line written by this writer is
-         * terminated with this separator. If not set, the platform-specific
-         * line separator is used.
-         *
-         * @param lineSeparator the line separator to be used
-         * @since 1.7
-         */
-        public void setLineSeparator(final String lineSeparator)
-        {
-            this.lineSeparator = lineSeparator;
-        }
-
-        /**
-         * Write a property.
-         *
-         * @param key the key of the property
-         * @param value the value of the property
-         *
-         * @throws IOException if an I/O error occurs
-         */
-        public void writeProperty(final String key, final Object value) throws IOException
-        {
-            writeProperty(key, value, false);
-        }
-
-        /**
-         * Write a property.
-         *
-         * @param key The key of the property
-         * @param values The array of values of the property
-         *
-         * @throws IOException if an I/O error occurs
-         */
-        public void writeProperty(final String key, final List<?> values) throws IOException
-        {
-            for (int i = 0; i < values.size(); i++)
-            {
-                writeProperty(key, values.get(i));
-            }
-        }
-
-        /**
-         * Writes the given property and its value. If the value happens to be a
-         * list, the {@code forceSingleLine} flag is evaluated. If it is
-         * set, all values are written on a single line using the list delimiter
-         * as separator.
-         *
-         * @param key the property key
-         * @param value the property value
-         * @param forceSingleLine the &quot;force single line&quot; flag
-         * @throws IOException if an error occurs
-         * @since 1.3
-         */
-        public void writeProperty(final String key, final Object value,
-                final boolean forceSingleLine) throws IOException
-        {
-            String v;
+        private String currentSeparator;
 
-            if (value instanceof List)
-            {
-                v = null;
-                final List<?> values = (List<?>) value;
-                if (forceSingleLine)
-                {
-                    try
-                    {
-                        v = String.valueOf(getDelimiterHandler()
-                                        .escapeList(values, valueTransformer));
-                    }
-                    catch (final UnsupportedOperationException uoex)
-                    {
-                        // the handler may not support escaping lists,
-                        // then the list is written in multiple lines
-                    }
-                }
-                if (v == null)
-                {
-                    writeProperty(key, values);
-                    return;
-                }
-            }
-            else
-            {
-                v = String.valueOf(getDelimiterHandler().escape(value, valueTransformer));
-            }
+        /** The global separator. If set, it overrides the current separator.*/
+        private String globalSeparator;
 
-            write(escapeKey(key));
-            write(fetchSeparator(key, value));
-            write(v);
+        /** The line separator.*/
+        private String lineSeparator;
 
-            writeln(null);
+        /**
+         * Creates a new instance of {@code PropertiesWriter}.
+         *
+         * @param writer a Writer object providing the underlying stream
+         * @param delHandler the delimiter handler for dealing with properties
+         *        with multiple values
+         */
+        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler)
+        {
+            this(writer, delHandler, DEFAULT_TRANSFORMER);
         }
 
         /**
-         * Write a comment.
+         * Creates a new instance of {@code PropertiesWriter}.
          *
-         * @param comment the comment to write
-         * @throws IOException if an I/O error occurs
+         * @param writer a Writer object providing the underlying stream
+         * @param delHandler the delimiter handler for dealing with properties
+         *        with multiple values
+         * @param valueTransformer the value transformer used to escape property values
          */
-        public void writeComment(final String comment) throws IOException
+        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler,
+            final ValueTransformer valueTransformer)
         {
-            writeln("# " + comment);
+            super(writer);
+            delimiterHandler = delHandler;
+            this.valueTransformer = valueTransformer;
         }
 
         /**
@@ -1313,23 +950,6 @@ public class PropertiesConfiguration extends BaseConfiguration
         }
 
         /**
-         * Helper method for writing a line with the platform specific line
-         * ending.
-         *
-         * @param s the content of the line (may be <b>null</b>)
-         * @throws IOException if an error occurs
-         * @since 1.3
-         */
-        public void writeln(final String s) throws IOException
-        {
-            if (s != null)
-            {
-                write(s);
-            }
-            write(getLineSeparator());
-        }
-
-        /**
          * Returns the separator to be used for the given property. This method
          * is called by {@code writeProperty()}. The string returned here
          * is used as separator between the property key and its value. Per
@@ -1349,336 +969,364 @@ public class PropertiesConfiguration extends BaseConfiguration
             return getGlobalSeparator() != null ? getGlobalSeparator()
                     : StringUtils.defaultString(getCurrentSeparator());
         }
-    } // class PropertiesWriter
 
-    /**
-     * <p>
-     * Definition of an interface that allows customization of read and write
-     * operations.
-     * </p>
-     * <p>
-     * For reading and writing properties files the inner classes
-     * {@code PropertiesReader} and {@code PropertiesWriter} are used.
-     * This interface defines factory methods for creating both a
-     * {@code PropertiesReader} and a {@code PropertiesWriter}. An
-     * object implementing this interface can be passed to the
-     * {@code setIOFactory()} method of
-     * {@code PropertiesConfiguration}. Every time the configuration is
-     * read or written the {@code IOFactory} is asked to create the
-     * appropriate reader or writer object. This provides an opportunity to
-     * inject custom reader or writer implementations.
-     * </p>
-     *
-     * @since 1.7
-     */
-    public interface IOFactory
-    {
         /**
-         * Creates a {@code PropertiesReader} for reading a properties
-         * file. This method is called whenever the
-         * {@code PropertiesConfiguration} is loaded. The reader returned
-         * by this method is then used for parsing the properties file.
+         * Returns the current property separator.
          *
-         * @param in the underlying reader (of the properties file)
-         * @return the {@code PropertiesReader} for loading the
-         *         configuration
+         * @return the current property separator
+         * @since 1.7
          */
-        PropertiesReader createPropertiesReader(Reader in);
+        public String getCurrentSeparator()
+        {
+            return currentSeparator;
+        }
 
         /**
-         * Creates a {@code PropertiesWriter} for writing a properties
-         * file. This method is called before the
-         * {@code PropertiesConfiguration} is saved. The writer returned by
-         * this method is then used for writing the properties file.
+         * Returns the delimiter handler for properties with multiple values.
+         * This object is used to escape property values so that they can be
+         * read in correctly the next time they are loaded.
          *
-         * @param out the underlying writer (to the properties file)
-         * @param handler the list delimiter delimiter for list parsing
-         * @return the {@code PropertiesWriter} for saving the
-         *         configuration
+         * @return the delimiter handler for properties with multiple values
+         * @since 2.0
          */
-        PropertiesWriter createPropertiesWriter(Writer out,
-                ListDelimiterHandler handler);
-    }
+        public ListDelimiterHandler getDelimiterHandler()
+        {
+            return delimiterHandler;
+        }
 
-    /**
-     * <p>
-     * A default implementation of the {@code IOFactory} interface.
-     * </p>
-     * <p>
-     * This class implements the {@code createXXXX()} methods defined by
-     * the {@code IOFactory} interface in a way that the default objects
-     * (i.e. {@code PropertiesReader} and {@code PropertiesWriter} are
-     * returned. Customizing either the reader or the writer (or both) can be
-     * done by extending this class and overriding the corresponding
-     * {@code createXXXX()} method.
-     * </p>
-     *
-     * @since 1.7
-     */
-    public static class DefaultIOFactory implements IOFactory
-    {
         /**
-         * The singleton instance.
+         * Returns the global property separator.
+         *
+         * @return the global property separator
+         * @since 1.7
          */
-        static final DefaultIOFactory INSTANCE = new DefaultIOFactory();
-
-        @Override
-        public PropertiesReader createPropertiesReader(final Reader in)
+        public String getGlobalSeparator()
         {
-            return new PropertiesReader(in);
+            return globalSeparator;
         }
 
-        @Override
-        public PropertiesWriter createPropertiesWriter(final Writer out,
-                final ListDelimiterHandler handler)
+        /**
+         * Returns the line separator.
+         *
+         * @return the line separator
+         * @since 1.7
+         */
+        public String getLineSeparator()
         {
-            return new PropertiesWriter(out, handler);
+            return lineSeparator != null ? lineSeparator : LINE_SEPARATOR;
         }
-    }
 
-    /**
-     * An alternative {@link IOFactory} that tries to mimic the behavior of
-     * {@link java.util.Properties} (Jup) more closely. The goal is to allow both of
-     * them be used interchangeably when reading and writing properties files
-     * without losing or changing information.
-     * <p>
-     * It also has the option to <em>not</em> use Unicode escapes. When using UTF-8
-     * encoding (which is e.g. the new default for resource bundle properties files
-     * since Java 9), Unicode escapes are no longer required and avoiding them makes
-     * properties files more readable with regular text editors.
-     * <p>
-     * Some of the ways this implementation differs from {@link DefaultIOFactory}:
-     * <ul>
-     * <li>Trailing whitespace will not be trimmed from each line.</li>
-     * <li>Unknown escape sequences will have their backslash removed.</li>
-     * <li>{@code \b} is not a recognized escape sequence.</li>
-     * <li>Leading spaces in property values are preserved by escaping them.</li>
-     * <li>All natural lines (i.e. in the file) of a logical property line will have
-     * their leading whitespace trimmed.</li>
-     * <li>Natural lines that look like comment lines within a logical line are not
-     * treated as such; they're part of the property value.</li>
-     * </ul>
-     *
-     * @since 2.4
-     */
-    public static class JupIOFactory implements IOFactory
-    {
+        /**
+         * Sets the current property separator. This separator is used when
+         * writing the next property.
+         *
+         * @param currentSeparator the current property separator
+         * @since 1.7
+         */
+        public void setCurrentSeparator(final String currentSeparator)
+        {
+            this.currentSeparator = currentSeparator;
+        }
 
         /**
-         * Whether characters less than {@code \u0020} and characters greater than
-         * {@code \u007E} in property keys or values should be escaped using
-         * Unicode escape sequences. Not necessary when e.g. writing as UTF-8.
+         * Sets the global property separator. This separator corresponds to the
+         * {@code globalSeparator} property of
+         * {@link PropertiesConfigurationLayout}. It defines the separator to be
+         * used for all properties. If it is undefined, the current separator is
+         * used.
+         *
+         * @param globalSeparator the global property separator
+         * @since 1.7
          */
-        private final boolean escapeUnicode;
+        public void setGlobalSeparator(final String globalSeparator)
+        {
+            this.globalSeparator = globalSeparator;
+        }
 
         /**
-         * Constructs a new {@link JupIOFactory} with Unicode escaping.
+         * Sets the line separator. Each line written by this writer is
+         * terminated with this separator. If not set, the platform-specific
+         * line separator is used.
+         *
+         * @param lineSeparator the line separator to be used
+         * @since 1.7
          */
-        public JupIOFactory()
+        public void setLineSeparator(final String lineSeparator)
         {
-            this(true);
+            this.lineSeparator = lineSeparator;
         }
 
         /**
-         * Constructs a new {@link JupIOFactory} with optional Unicode escaping. Whether
-         * Unicode escaping is required depends on the encoding used to save the
-         * properties file. E.g. for ISO-8859-1 this must be turned on, for UTF-8 it's
-         * not necessary. Unfortunately this factory can't determine the encoding on its
-         * own.
+         * Write a comment.
          *
-         * @param escapeUnicode whether Unicode characters should be escaped
+         * @param comment the comment to write
+         * @throws IOException if an I/O error occurs
          */
-        public JupIOFactory(final boolean escapeUnicode)
+        public void writeComment(final String comment) throws IOException
         {
-            this.escapeUnicode = escapeUnicode;
+            writeln("# " + comment);
         }
 
-        @Override
-        public PropertiesReader createPropertiesReader(final Reader in)
+        /**
+         * Helper method for writing a line with the platform specific line
+         * ending.
+         *
+         * @param s the content of the line (may be <b>null</b>)
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public void writeln(final String s) throws IOException
         {
-            return new JupPropertiesReader(in);
+            if (s != null)
+            {
+                write(s);
+            }
+            write(getLineSeparator());
         }
 
-        @Override
-        public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler)
+        /**
+         * Write a property.
+         *
+         * @param key The key of the property
+         * @param values The array of values of the property
+         *
+         * @throws IOException if an I/O error occurs
+         */
+        public void writeProperty(final String key, final List<?> values) throws IOException
         {
-            return new JupPropertiesWriter(out, handler, escapeUnicode);
+            for (int i = 0; i < values.size(); i++)
+            {
+                writeProperty(key, values.get(i));
+            }
         }
 
-    }
-
-    /**
-     * A {@link PropertiesReader} that tries to mimic the behavior of
-     * {@link java.util.Properties}.
-     *
-     * @since 2.4
-     */
-    public static class JupPropertiesReader extends PropertiesReader
-    {
-
         /**
-         * Constructor.
+         * Write a property.
          *
-         * @param reader A Reader.
+         * @param key the key of the property
+         * @param value the value of the property
+         *
+         * @throws IOException if an I/O error occurs
          */
-        public JupPropertiesReader(final Reader reader)
+        public void writeProperty(final String key, final Object value) throws IOException
         {
-            super(reader);
+            writeProperty(key, value, false);
         }
 
-
-        @Override
-        public String readProperty() throws IOException
+        /**
+         * Writes the given property and its value. If the value happens to be a
+         * list, the {@code forceSingleLine} flag is evaluated. If it is
+         * set, all values are written on a single line using the list delimiter
+         * as separator.
+         *
+         * @param key the property key
+         * @param value the property value
+         * @param forceSingleLine the &quot;force single line&quot; flag
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public void writeProperty(final String key, final Object value,
+                final boolean forceSingleLine) throws IOException
         {
-            getCommentLines().clear();
-            final StringBuilder buffer = new StringBuilder();
+            String v;
 
-            while (true)
+            if (value instanceof List)
             {
-                String line = readLine();
-                if (line == null)
+                v = null;
+                final List<?> values = (List<?>) value;
+                if (forceSingleLine)
                 {
-                    // EOF
-                    if (buffer.length() > 0)
+                    try
                     {
-                        break;
+                        v = String.valueOf(getDelimiterHandler()
+                                        .escapeList(values, valueTransformer));
                     }
-                    return null;
-                }
-
-                // while a property line continues there are no comments (even if the line from
-                // the file looks like one)
-                if (isCommentLine(line) && (buffer.length() == 0))
-                {
-                    getCommentLines().add(line);
-                    continue;
-                }
-
-                // while property line continues left trim all following lines read from the
-                // file
-                if (buffer.length() > 0)
-                {
-                    // index of the first non-whitespace character
-                    int i;
-                    for (i = 0; i < line.length(); i++)
+                    catch (final UnsupportedOperationException uoex)
                     {
-                        if (!Character.isWhitespace(line.charAt(i)))
-                        {
-                            break;
-                        }
+                        // the handler may not support escaping lists,
+                        // then the list is written in multiple lines
                     }
-
-                    line = line.substring(i);
-                }
-
-                if (checkCombineLines(line))
-                {
-                    line = line.substring(0, line.length() - 1);
-                    buffer.append(line);
                 }
-                else
+                if (v == null)
                 {
-                    buffer.append(line);
-                    break;
+                    writeProperty(key, values);
+                    return;
                 }
             }
-            return buffer.toString();
-        }
+            else
+            {
+                v = String.valueOf(getDelimiterHandler().escape(value, valueTransformer));
+            }
 
-        @Override
-        protected void parseProperty(final String line)
-        {
-            final String[] property = doParseProperty(line, false);
-            initPropertyName(property[0]);
-            initPropertyValue(property[1]);
-            initPropertySeparator(property[2]);
+            write(escapeKey(key));
+            write(fetchSeparator(key, value));
+            write(v);
+
+            writeln(null);
         }
+    } // class PropertiesWriter
 
-        @Override
-        protected String unescapePropertyValue(final String value)
+    /**
+     * Defines default error handling for the special {@code "include"} key by throwing the given exception.
+     *
+     * @since 2.6
+     */
+    public static final ConfigurationConsumer<ConfigurationException> DEFAULT_INCLUDE_LISTENER = e -> { throw e; };
+
+    /**
+     * Defines error handling as a noop for the special {@code "include"} key.
+     *
+     * @since 2.6
+     */
+    public static final ConfigurationConsumer<ConfigurationException> NOOP_INCLUDE_LISTENER = e -> { /* noop */ };
+
+    /**
+     * The default encoding (ISO-8859-1 as specified by
+     * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
+     */
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+    /** Constant for the supported comment characters.*/
+    static final String COMMENT_CHARS = "#!";
+
+    /** Constant for the default properties separator.*/
+    static final String DEFAULT_SEPARATOR = " = ";
+
+    /**
+     * A string with special characters that need to be unescaped when reading
+     * a properties file. {@code java.util.Properties} escapes these characters
+     * when writing out a properties file.
+     */
+    private static final String UNESCAPE_CHARACTERS = ":#=!\\\'\"";
+
+    /**
+     * This is the name of the property that can point to other
+     * properties file for including other properties files.
+     */
+    private static String include = "include";
+
+    /**
+     * This is the name of the property that can point to other
+     * properties file for including other properties files.
+     * <p>
+     * If the file is absent, processing continues normally.
+     * </p>
+     */
+    private static String includeOptional = "includeoptional";
+
+    /** The list of possible key/value separators */
+    private static final char[] SEPARATORS = new char[] {'=', ':'};
+
+    /** The white space characters used as key/value separators. */
+    private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
+
+    /** Constant for the platform specific line separator.*/
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** Constant for the radix of hex numbers.*/
+    private static final int HEX_RADIX = 16;
+
+    /** Constant for the length of a unicode literal.*/
+    private static final int UNICODE_LEN = 4;
+
+    /**
+     * Returns the number of trailing backslashes. This is sometimes needed for
+     * the correct handling of escape characters.
+     *
+     * @param line the string to investigate
+     * @return the number of trailing backslashes
+     */
+    private static int countTrailingBS(final String line)
+    {
+        int bsCount = 0;
+        for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
         {
-            return unescapeJava(value, true);
+            bsCount++;
         }
 
+        return bsCount;
+    }
+
+    /**
+     * Gets the property value for including other properties files.
+     * By default it is "include".
+     *
+     * @return A String.
+     */
+    public static String getInclude()
+    {
+        return PropertiesConfiguration.include;
+    }
+
+    /**
+     * Gets the property value for including other properties files.
+     * By default it is "includeoptional".
+     * <p>
+     * If the file is absent, processing continues normally.
+     * </p>
+     *
+     * @return A String.
+     * @since 2.5
+     */
+    public static String getIncludeOptional()
+    {
+        return PropertiesConfiguration.includeOptional;
+    }
+
+    /**
+     * Tests whether a line is a comment, i.e. whether it starts with a comment
+     * character.
+     *
+     * @param line the line
+     * @return a flag if this is a comment line
+     * @since 1.3
+     */
+    static boolean isCommentLine(final String line)
+    {
+        final String s = line.trim();
+        // blanc lines are also treated as comment lines
+        return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
     }
 
     /**
-     * A {@link PropertiesWriter} that tries to mimic the behavior of
-     * {@link java.util.Properties}.
+     * Checks whether the specified character needs to be unescaped. This method
+     * is called when during reading a property file an escape character ('\')
+     * is detected. If the character following the escape character is
+     * recognized as a special character which is escaped per default in a Java
+     * properties file, it has to be unescaped.
      *
-     * @since 2.4
+     * @param ch the character in question
+     * @return a flag whether this character has to be unescaped
      */
-    public static class JupPropertiesWriter extends PropertiesWriter
+    private static boolean needsUnescape(final char ch)
     {
+        return UNESCAPE_CHARACTERS.indexOf(ch) >= 0;
+    }
 
-        /**
-         * The starting ASCII printable character.
-         */
-        private static final int PRINTABLE_INDEX_END = 0x7e;
-
-        /**
-         * The ending ASCII printable character.
-         */
-        private static final int PRINTABLE_INDEX_START = 0x20;
-
-        /**
-         * A UnicodeEscaper for characters outside the ASCII printable range.
-         */
-        private static final UnicodeEscaper ESCAPER = UnicodeEscaper.outsideOf(PRINTABLE_INDEX_START,
-            PRINTABLE_INDEX_END);
-
-        /**
-         * Characters that need to be escaped when wring a properties file.
-         */
-        private static final Map<CharSequence, CharSequence> JUP_CHARS_ESCAPE;
-        static
-        {
-            final Map<CharSequence, CharSequence> initialMap = new HashMap<>();
-            initialMap.put("\\", "\\\\");
-            initialMap.put("\n", "\\n");
-            initialMap.put("\t", "\\t");
-            initialMap.put("\f", "\\f");
-            initialMap.put("\r", "\\r");
-            JUP_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap);
-        }
-
-        /**
-         * Creates a new instance of {@code JupPropertiesWriter}.
-         *
-         * @param writer a Writer object providing the underlying stream
-         * @param delHandler the delimiter handler for dealing with properties with
-         *        multiple values
-         * @param escapeUnicode whether Unicode characters should be escaped using
-         *        Unicode escapes
-         */
-        public JupPropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler,
-            final boolean escapeUnicode)
-        {
-            super(writer, delHandler, value -> {
-                String valueString = String.valueOf(value);
-
-                CharSequenceTranslator translator;
-                if (escapeUnicode)
-                {
-                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE), ESCAPER);
-                }
-                else
-                {
-                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE));
-                }
-
-                valueString = translator.translate(valueString);
-
-                // escape the first leading space to preserve it (and all after it)
-                if (valueString.startsWith(" "))
-                {
-                    valueString = "\\" + valueString;
-                }
-
-                return valueString;
-            });
-        }
+    /**
+     * Sets the property value for including other properties files.
+     * By default it is "include".
+     *
+     * @param inc A String.
+     */
+    public static void setInclude(final String inc)
+    {
+        PropertiesConfiguration.include = inc;
+    }
 
+    /**
+     * Sets the property value for including other properties files.
+     * By default it is "include".
+     * <p>
+     * If the file is absent, processing continues normally.
+     * </p>
+     *
+     * @param inc A String.
+     * @since 2.5
+     */
+    public static void setIncludeOptional(final String inc)
+    {
+        PropertiesConfiguration.includeOptional = inc;
     }
 
     /**
@@ -1795,39 +1443,199 @@ public class PropertiesConfiguration extends BaseConfiguration
                     out.append(ch);
                 }
 
-                continue;
-            }
-            else if (ch == '\\')
-            {
-                hadSlash = true;
-                continue;
-            }
-            out.append(ch);
-        }
+                continue;
+            }
+            else if (ch == '\\')
+            {
+                hadSlash = true;
+                continue;
+            }
+            out.append(ch);
+        }
+
+        if (hadSlash)
+        {
+            // then we're in the weird case of a \ at the end of the
+            // string, let's output it anyway.
+            out.append('\\');
+        }
+
+        return out.toString();
+    }
+
+    /** Stores the layout object.*/
+    private PropertiesConfigurationLayout layout;
+
+    /** The include listener for the special {@code "include"} key. */
+    private ConfigurationConsumer<ConfigurationException> includeListener;
+
+    /** The IOFactory for creating readers and writers.*/
+    private IOFactory ioFactory;
+
+    /** The current {@code FileLocator}. */
+    private FileLocator locator;
+
+    /** Allow file inclusion or not */
+    private boolean includesAllowed = true;
+
+    /**
+     * Creates an empty PropertyConfiguration object which can be
+     * used to synthesize a new Properties file by adding values and
+     * then saving().
+     */
+    public PropertiesConfiguration()
+    {
+        installLayout(createLayout());
+    }
+
+    /**
+     * Creates a copy of this object.
+     *
+     * @return the copy
+     */
+    @Override
+    public Object clone()
+    {
+        final PropertiesConfiguration copy = (PropertiesConfiguration) super.clone();
+        if (layout != null)
+        {
+            copy.setLayout(new PropertiesConfigurationLayout(layout));
+        }
+        return copy;
+    }
+
+    /**
+     * Creates a standard layout object. This configuration is initialized with
+     * such a standard layout.
+     *
+     * @return the newly created layout object
+     */
+    private PropertiesConfigurationLayout createLayout()
+    {
+        return new PropertiesConfigurationLayout();
+    }
+
+    /**
+     * Returns the footer comment. This is a comment at the very end of the
+     * file.
+     *
+     * @return the footer comment
+     * @since 2.0
+     */
+    public String getFooter()
+    {
+        beginRead(false);
+        try
+        {
+            return getLayout().getFooterComment();
+        }
+        finally
+        {
+            endRead();
+        }
+    }
+
+    /**
+     * Return the comment header.
+     *
+     * @return the comment header
+     * @since 1.1
+     */
+    public String getHeader()
+    {
+        beginRead(false);
+        try
+        {
+            return getLayout().getHeaderComment();
+        }
+        finally
+        {
+            endRead();
+        }
+    }
+
+    /**
+     * Gets the current include listener, never null.
+     *
+     * @return the current include listener, never null.
+     * @since 2.6
+     */
+    public ConfigurationConsumer<ConfigurationException> getIncludeListener()
+    {
+        return includeListener != null ? includeListener : PropertiesConfiguration.DEFAULT_INCLUDE_LISTENER;
+    }
+
+    /**
+     * Returns the {@code IOFactory} to be used for creating readers and
+     * writers when loading or saving this configuration.
+     *
+     * @return the {@code IOFactory}
+     * @since 1.7
+     */
+    public IOFactory getIOFactory()
+    {
+        return ioFactory != null ? ioFactory : DefaultIOFactory.INSTANCE;
+    }
+
+    /**
+     * Returns the associated layout object.
+     *
+     * @return the associated layout object
+     * @since 1.3
+     */
+    public PropertiesConfigurationLayout getLayout()
+    {
+        return layout;
+    }
+
+    /**
+     * Stores the current {@code FileLocator} for a following IO operation. The
+     * {@code FileLocator} is needed to resolve include files with relative file
+     * names.
+     *
+     * @param locator the current {@code FileLocator}
+     * @since 2.0
+     */
+    @Override
+    public void initFileLocator(final FileLocator locator)
+    {
+        this.locator = locator;
+    }
 
-        if (hadSlash)
+    /**
+     * Installs a layout object. It has to be ensured that the layout is
+     * registered as change listener at this configuration. If there is already
+     * a layout object installed, it has to be removed properly.
+     *
+     * @param layout the layout object to be installed
+     */
+    private void installLayout(final PropertiesConfigurationLayout layout)
+    {
+        // only one layout must exist
+        if (this.layout != null)
         {
-            // then we're in the weird case of a \ at the end of the
-            // string, let's output it anyway.
-            out.append('\\');
+            removeEventListener(ConfigurationEvent.ANY, this.layout);
         }
 
-        return out.toString();
+        if (layout == null)
+        {
+            this.layout = createLayout();
+        }
+        else
+        {
+            this.layout = layout;
+        }
+        addEventListener(ConfigurationEvent.ANY, this.layout);
     }
 
     /**
-     * Checks whether the specified character needs to be unescaped. This method
-     * is called when during reading a property file an escape character ('\')
-     * is detected. If the character following the escape character is
-     * recognized as a special character which is escaped per default in a Java
-     * properties file, it has to be unescaped.
+     * Reports the status of file inclusion.
      *
-     * @param ch the character in question
-     * @return a flag whether this character has to be unescaped
+     * @return True if include files are loaded.
      */
-    private static boolean needsUnescape(final char ch)
+    public boolean isIncludesAllowed()
     {
-        return UNESCAPE_CHARACTERS.indexOf(ch) >= 0;
+        return this.includesAllowed;
     }
 
     /**
@@ -1926,4 +1734,196 @@ public class PropertiesConfiguration extends BaseConfiguration
         return FileLocatorUtils.locate(includeLocator);
     }
 
+    /**
+     * This method is invoked by the associated
+     * {@link PropertiesConfigurationLayout} object for each
+     * property definition detected in the parsed properties file. Its task is
+     * to check whether this is a special property definition (e.g. the
+     * {@code include} property). If not, the property must be added to
+     * this configuration. The return value indicates whether the property
+     * should be treated as a normal property. If it is <b>false</b>, the
+     * layout object will ignore this property.
+     *
+     * @param key the property key
+     * @param value the property value
+     * @param seenStack the stack of seen include URLs
+     * @return a flag whether this is a normal property
+     * @throws ConfigurationException if an error occurs
+     * @since 1.3
+     */
+    boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack)
+            throws ConfigurationException
+    {
+        boolean result;
+
+        if (StringUtils.isNotEmpty(getInclude())
+                && key.equalsIgnoreCase(getInclude()))
+        {
+            if (isIncludesAllowed())
+            {
+                final Collection<String> files =
+                        getListDelimiterHandler().split(value, true);
+                for (final String f : files)
+                {
+                    loadIncludeFile(interpolate(f), false, seenStack);
+                }
+            }
+            result = false;
+        }
+
+        else if (StringUtils.isNotEmpty(getIncludeOptional())
+            && key.equalsIgnoreCase(getIncludeOptional()))
+        {
+            if (isIncludesAllowed())
+            {
+                final Collection<String> files =
+                        getListDelimiterHandler().split(value, true);
+                for (final String f : files)
+                {
+                    loadIncludeFile(interpolate(f), true, seenStack);
+                }
+            }
+            result = false;
+        }
+
+        else
+        {
+            addPropertyInternal(key, value);
+            result = true;
+        }
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc} This implementation delegates to the associated layout
+     * object which does the actual loading. Note that this method does not
+     * do any synchronization. This lies in the responsibility of the caller.
+     * (Typically, the caller is a {@code FileHandler} object which takes
+     * care for proper synchronization.)
+     *
+     * @since 2.0
+     */
+    @Override
+    public void read(final Reader in) throws ConfigurationException, IOException
+    {
+        getLayout().load(this, in);
+    }
+
+    /**
+     * Sets the footer comment. If set, this comment is written after all
+     * properties at the end of the file.
+     *
+     * @param footer the footer comment
+     * @since 2.0
+     */
+    public void setFooter(final String footer)
+    {
+        beginWrite(false);
+        try
+        {
+            getLayout().setFooterComment(footer);
+        }
+        finally
+        {
+            endWrite();
+        }
+    }
+
+    /**
+     * Set the comment header.
+     *
+     * @param header the header to use
+     * @since 1.1
+     */
+    public void setHeader(final String header)
+    {
+        beginWrite(false);
+        try
+        {
+            getLayout().setHeaderComment(header);
+        }
+        finally
+        {
+            endWrite();
+        }
+    }
+
+    /**
+     * Sets the current include listener, may not be null.
+     *
+     * @param includeListener the current include listener, may not be null.
+     * @throws IllegalArgumentException if the {@code includeListener} is null.
+     * @since 2.6
+     */
+    public void setIncludeListener(final ConfigurationConsumer<ConfigurationException> includeListener)
+    {
+        if (includeListener == null)
+        {
+            throw new IllegalArgumentException("includeListener must not be null.");
+        }
+        this.includeListener = includeListener;
+    }
+
+    /**
+     * Controls whether additional files can be loaded by the {@code include = <xxx>}
+     * statement or not. This is <b>true</b> per default.
+     *
+     * @param includesAllowed True if Includes are allowed.
+     */
+    public void setIncludesAllowed(final boolean includesAllowed)
+    {
+        this.includesAllowed = includesAllowed;
+    }
+
+    /**
+     * Sets the {@code IOFactory} to be used for creating readers and
+     * writers when loading or saving this configuration. Using this method a
+     * client can customize the reader and writer classes used by the load and
+     * save operations. Note that this method must be called before invoking
+     * one of the {@code load()} and {@code save()} methods.
+     * Especially, if you want to use a custom {@code IOFactory} for
+     * changing the {@code PropertiesReader}, you cannot load the
+     * configuration data in the constructor.
+     *
+     * @param ioFactory the new {@code IOFactory} (must not be <b>null</b>)
+     * @throws IllegalArgumentException if the {@code IOFactory} is
+     *         <b>null</b>
+     * @since 1.7
+     */
+    public void setIOFactory(final IOFactory ioFactory)
+    {
+        if (ioFactory == null)
+        {
+            throw new IllegalArgumentException("IOFactory must not be null.");
+        }
+
+        this.ioFactory = ioFactory;
+    }
+
+    /**
+     * Sets the associated layout object.
+     *
+     * @param layout the new layout object; can be <b>null</b>, then a new
+     * layout object will be created
+     * @since 1.3
+     */
+    public void setLayout(final PropertiesConfigurationLayout layout)
+    {
+        installLayout(layout);
+    }
+
+    /**
+     * {@inheritDoc} This implementation delegates to the associated layout
+     * object which does the actual saving. Note that, analogous to
+     * {@link #read(Reader)}, this method does not do any synchronization.
+     *
+     * @since 2.0
+     */
+    @Override
+    public void write(final Writer out) throws ConfigurationException, IOException
+    {
+        getLayout().save(this, out);
+    }
+
 }


[commons-configuration] 02/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit dd735efced61e331c134627015177f095785e2ba
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:42:49 2020 -0400

    Sort members.
---
 .../builder/FileBasedBuilderParametersImpl.java    | 246 ++++++++++-----------
 1 file changed, 123 insertions(+), 123 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderParametersImpl.java b/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderParametersImpl.java
index 9b6c0a5..7b52431 100644
--- a/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderParametersImpl.java
+++ b/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderParametersImpl.java
@@ -60,35 +60,29 @@ public class FileBasedBuilderParametersImpl extends BasicBuilderParameters
             "reloadingDetectorFactory";
 
     /**
-     * Stores the associated file handler for the location of the configuration.
-     */
-    private FileHandler fileHandler;
-
-    /** The factory for reloading detectors. */
-    private ReloadingDetectorFactory reloadingDetectorFactory;
-
-    /** The refresh delay for reloading support. */
-    private Long reloadingRefreshDelay;
-
-    /**
-     * Creates a new instance of {@code FileBasedBuilderParametersImpl} with an
-     * uninitialized {@code FileHandler} object.
-     */
-    public FileBasedBuilderParametersImpl()
-    {
-        this(null);
-    }
-
-    /**
-     * Creates a new instance of {@code FileBasedBuilderParametersImpl} and
-     * associates it with the given {@code FileHandler} object. If the handler
-     * is <b>null</b>, a new handler instance is created.
+     * Creates a new {@code FileBasedBuilderParametersImpl} object from the
+     * content of the given map. While {@code fromParameters()} expects that an
+     * object already exists and is stored in the given map, this method creates
+     * a new instance based on the content of the map. The map can contain
+     * properties of a {@code FileHandler} and some additional settings which
+     * are stored directly in the newly created object. If the map is
+     * <b>null</b>, an uninitialized instance is returned.
      *
-     * @param handler the associated {@code FileHandler} (can be <b>null</b>)
+     * @param map the map with properties (must not be <b>null</b>)
+     * @return the newly created instance
+     * @throws ClassCastException if the map contains invalid data
      */
-    public FileBasedBuilderParametersImpl(final FileHandler handler)
+    public static FileBasedBuilderParametersImpl fromMap(final Map<String, ?> map)
     {
-        fileHandler = handler != null ? handler : new FileHandler();
+        final FileBasedBuilderParametersImpl params =
+                new FileBasedBuilderParametersImpl(FileHandler.fromMap(map));
+        if (map != null)
+        {
+            params.setReloadingRefreshDelay((Long) map.get(PROP_REFRESH_DELAY));
+            params.setReloadingDetectorFactory((ReloadingDetectorFactory) map
+                    .get(PROP_DETECTOR_FACTORY));
+        }
+        return params;
     }
 
     /**
@@ -137,60 +131,49 @@ public class FileBasedBuilderParametersImpl extends BasicBuilderParameters
     }
 
     /**
-     * Creates a new {@code FileBasedBuilderParametersImpl} object from the
-     * content of the given map. While {@code fromParameters()} expects that an
-     * object already exists and is stored in the given map, this method creates
-     * a new instance based on the content of the map. The map can contain
-     * properties of a {@code FileHandler} and some additional settings which
-     * are stored directly in the newly created object. If the map is
-     * <b>null</b>, an uninitialized instance is returned.
+     * Stores the associated file handler for the location of the configuration.
+     */
+    private FileHandler fileHandler;
+
+    /** The factory for reloading detectors. */
+    private ReloadingDetectorFactory reloadingDetectorFactory;
+
+    /** The refresh delay for reloading support. */
+    private Long reloadingRefreshDelay;
+
+    /**
+     * Creates a new instance of {@code FileBasedBuilderParametersImpl} with an
+     * uninitialized {@code FileHandler} object.
+     */
+    public FileBasedBuilderParametersImpl()
+    {
+        this(null);
+    }
+
+    /**
+     * Creates a new instance of {@code FileBasedBuilderParametersImpl} and
+     * associates it with the given {@code FileHandler} object. If the handler
+     * is <b>null</b>, a new handler instance is created.
      *
-     * @param map the map with properties (must not be <b>null</b>)
-     * @return the newly created instance
-     * @throws ClassCastException if the map contains invalid data
+     * @param handler the associated {@code FileHandler} (can be <b>null</b>)
      */
-    public static FileBasedBuilderParametersImpl fromMap(final Map<String, ?> map)
+    public FileBasedBuilderParametersImpl(final FileHandler handler)
     {
-        final FileBasedBuilderParametersImpl params =
-                new FileBasedBuilderParametersImpl(FileHandler.fromMap(map));
-        if (map != null)
-        {
-            params.setReloadingRefreshDelay((Long) map.get(PROP_REFRESH_DELAY));
-            params.setReloadingDetectorFactory((ReloadingDetectorFactory) map
-                    .get(PROP_DETECTOR_FACTORY));
-        }
-        return params;
+        fileHandler = handler != null ? handler : new FileHandler();
     }
 
     /**
-     * {@inheritDoc} This implementation takes some properties defined in this
-     * class into account.
+     * {@inheritDoc} This implementation also creates a copy of the
+     * {@code FileHandler}.
      */
     @Override
-    public void inheritFrom(final Map<String, ?> source)
+    public FileBasedBuilderParametersImpl clone()
     {
-        super.inheritFrom(source);
-
-        final FileBasedBuilderParametersImpl srcParams = fromParameters(source);
-        if (srcParams != null)
-        {
-            setFileSystem(srcParams.getFileHandler().getFileSystem());
-            setLocationStrategy(
-                    srcParams.getFileHandler().getLocationStrategy());
-            if (srcParams.getFileHandler().getEncoding() != null)
-            {
-                setEncoding(srcParams.getFileHandler().getEncoding());
-            }
-            if (srcParams.getReloadingDetectorFactory() != null)
-            {
-                setReloadingDetectorFactory(
-                        srcParams.getReloadingDetectorFactory());
-            }
-            if (srcParams.getReloadingRefreshDelay() != null)
-            {
-                setReloadingRefreshDelay(srcParams.getReloadingRefreshDelay());
-            }
-        }
+        final FileBasedBuilderParametersImpl copy =
+                (FileBasedBuilderParametersImpl) super.clone();
+        copy.fileHandler =
+                new FileHandler(fileHandler.getContent(), fileHandler);
+        return copy;
     }
 
     /**
@@ -205,22 +188,18 @@ public class FileBasedBuilderParametersImpl extends BasicBuilderParameters
     }
 
     /**
-     * Returns the refresh delay for reload operations. Result may be
-     * <b>null</b> if this value has not been set.
-     *
-     * @return the reloading refresh delay
+     * {@inheritDoc} This implementation returns a map which contains this
+     * object itself under a specific key. The static {@code fromParameters()}
+     * method can be used to extract an instance from a parameters map. Of
+     * course, the properties inherited from the base class are also added to
+     * the result map.
      */
-    public Long getReloadingRefreshDelay()
-    {
-        return reloadingRefreshDelay;
-    }
-
     @Override
-    public FileBasedBuilderParametersImpl setReloadingRefreshDelay(
-            final Long reloadingRefreshDelay)
+    public Map<String, Object> getParameters()
     {
-        this.reloadingRefreshDelay = reloadingRefreshDelay;
-        return this;
+        final Map<String, Object> params = super.getParameters();
+        params.put(PARAM_KEY, this);
+        return params;
     }
 
     /**
@@ -234,46 +213,73 @@ public class FileBasedBuilderParametersImpl extends BasicBuilderParameters
         return reloadingDetectorFactory;
     }
 
-    @Override
-    public FileBasedBuilderParametersImpl setReloadingDetectorFactory(
-            final ReloadingDetectorFactory reloadingDetectorFactory)
+    /**
+     * Returns the refresh delay for reload operations. Result may be
+     * <b>null</b> if this value has not been set.
+     *
+     * @return the reloading refresh delay
+     */
+    public Long getReloadingRefreshDelay()
     {
-        this.reloadingDetectorFactory = reloadingDetectorFactory;
-        return this;
+        return reloadingRefreshDelay;
     }
 
+    /**
+     * {@inheritDoc} This implementation takes some properties defined in this
+     * class into account.
+     */
     @Override
-    public FileBasedBuilderParametersImpl setFile(final File file)
+    public void inheritFrom(final Map<String, ?> source)
     {
-        getFileHandler().setFile(file);
-        return this;
+        super.inheritFrom(source);
+
+        final FileBasedBuilderParametersImpl srcParams = fromParameters(source);
+        if (srcParams != null)
+        {
+            setFileSystem(srcParams.getFileHandler().getFileSystem());
+            setLocationStrategy(
+                    srcParams.getFileHandler().getLocationStrategy());
+            if (srcParams.getFileHandler().getEncoding() != null)
+            {
+                setEncoding(srcParams.getFileHandler().getEncoding());
+            }
+            if (srcParams.getReloadingDetectorFactory() != null)
+            {
+                setReloadingDetectorFactory(
+                        srcParams.getReloadingDetectorFactory());
+            }
+            if (srcParams.getReloadingRefreshDelay() != null)
+            {
+                setReloadingRefreshDelay(srcParams.getReloadingRefreshDelay());
+            }
+        }
     }
 
     @Override
-    public FileBasedBuilderParametersImpl setURL(final URL url)
+    public FileBasedBuilderParametersImpl setBasePath(final String path)
     {
-        getFileHandler().setURL(url);
+        getFileHandler().setBasePath(path);
         return this;
     }
 
     @Override
-    public FileBasedBuilderParametersImpl setPath(final String path)
+    public FileBasedBuilderParametersImpl setEncoding(final String enc)
     {
-        getFileHandler().setPath(path);
+        getFileHandler().setEncoding(enc);
         return this;
     }
 
     @Override
-    public FileBasedBuilderParametersImpl setFileName(final String name)
+    public FileBasedBuilderParametersImpl setFile(final File file)
     {
-        getFileHandler().setFileName(name);
+        getFileHandler().setFile(file);
         return this;
     }
 
     @Override
-    public FileBasedBuilderParametersImpl setBasePath(final String path)
+    public FileBasedBuilderParametersImpl setFileName(final String name)
     {
-        getFileHandler().setBasePath(path);
+        getFileHandler().setFileName(name);
         return this;
     }
 
@@ -293,38 +299,32 @@ public class FileBasedBuilderParametersImpl extends BasicBuilderParameters
     }
 
     @Override
-    public FileBasedBuilderParametersImpl setEncoding(final String enc)
+    public FileBasedBuilderParametersImpl setPath(final String path)
     {
-        getFileHandler().setEncoding(enc);
+        getFileHandler().setPath(path);
         return this;
     }
 
-    /**
-     * {@inheritDoc} This implementation returns a map which contains this
-     * object itself under a specific key. The static {@code fromParameters()}
-     * method can be used to extract an instance from a parameters map. Of
-     * course, the properties inherited from the base class are also added to
-     * the result map.
-     */
     @Override
-    public Map<String, Object> getParameters()
+    public FileBasedBuilderParametersImpl setReloadingDetectorFactory(
+            final ReloadingDetectorFactory reloadingDetectorFactory)
     {
-        final Map<String, Object> params = super.getParameters();
-        params.put(PARAM_KEY, this);
-        return params;
+        this.reloadingDetectorFactory = reloadingDetectorFactory;
+        return this;
     }
 
-    /**
-     * {@inheritDoc} This implementation also creates a copy of the
-     * {@code FileHandler}.
-     */
     @Override
-    public FileBasedBuilderParametersImpl clone()
+    public FileBasedBuilderParametersImpl setReloadingRefreshDelay(
+            final Long reloadingRefreshDelay)
     {
-        final FileBasedBuilderParametersImpl copy =
-                (FileBasedBuilderParametersImpl) super.clone();
-        copy.fileHandler =
-                new FileHandler(fileHandler.getContent(), fileHandler);
-        return copy;
+        this.reloadingRefreshDelay = reloadingRefreshDelay;
+        return this;
+    }
+
+    @Override
+    public FileBasedBuilderParametersImpl setURL(final URL url)
+    {
+        getFileHandler().setURL(url);
+        return this;
     }
 }


[commons-configuration] 01/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit 34b7cdf5a8065aba904def85d4c4fa065caeb114
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:39:55 2020 -0400

    Sort members.
---
 .../builder/FileBasedBuilderProperties.java        | 64 +++++++++++-----------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderProperties.java b/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderProperties.java
index 4bba62b..8cfc64b 100644
--- a/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderProperties.java
+++ b/src/main/java/org/apache/commons/configuration2/builder/FileBasedBuilderProperties.java
@@ -36,23 +36,20 @@ import org.apache.commons.configuration2.io.FileSystem;
 public interface FileBasedBuilderProperties<T>
 {
     /**
-     * Sets the refresh delay for reloading support
+     * Sets the base path of the associated {@code FileHandler}.
      *
-     * @param reloadingRefreshDelay the refresh delay (in milliseconds)
+     * @param path the base path
      * @return a reference to this object for method chaining
      */
-    T setReloadingRefreshDelay(Long reloadingRefreshDelay);
+    T setBasePath(String path);
 
     /**
-     * Sets the factory for creating {@code ReloadingDetector} objects. With
-     * this method a custom factory for reloading detectors can be installed.
-     * Per default, a factory creating {@code FileHandlerReloadingDetector}
-     * objects is used.
+     * Sets the encoding of the associated {@code FileHandler}.
      *
-     * @param factory the {@code ReloadingDetectorFactory}
+     * @param enc the encoding
      * @return a reference to this object for method chaining
      */
-    T setReloadingDetectorFactory(ReloadingDetectorFactory factory);
+    T setEncoding(String enc);
 
     /**
      * Sets the location of the associated {@code FileHandler} as a {@code File}
@@ -64,60 +61,63 @@ public interface FileBasedBuilderProperties<T>
     T setFile(File file);
 
     /**
-     * Sets the location of the associated {@code FileHandler} as a {@code URL}
-     * object.
+     * Sets the file name of the associated {@code FileHandler}.
      *
-     * @param url the {@code URL} location
+     * @param name the file name
      * @return a reference to this object for method chaining
      */
-    T setURL(URL url);
+    T setFileName(String name);
 
     /**
-     * Sets the location of the associated {@code FileHandler} as an absolute
-     * file path.
+     * Sets the {@code FileSystem} of the associated {@code FileHandler}.
      *
-     * @param path the path location
+     * @param fs the {@code FileSystem}
      * @return a reference to this object for method chaining
      */
-    T setPath(String path);
+    T setFileSystem(FileSystem fs);
 
     /**
-     * Sets the file name of the associated {@code FileHandler}.
+     * Sets the {@code FileLocationStrategy} for resolving the referenced file.
      *
-     * @param name the file name
+     * @param strategy the {@code FileLocationStrategy}
      * @return a reference to this object for method chaining
      */
-    T setFileName(String name);
+    T setLocationStrategy(FileLocationStrategy strategy);
 
     /**
-     * Sets the base path of the associated {@code FileHandler}.
+     * Sets the location of the associated {@code FileHandler} as an absolute
+     * file path.
      *
-     * @param path the base path
+     * @param path the path location
      * @return a reference to this object for method chaining
      */
-    T setBasePath(String path);
+    T setPath(String path);
 
     /**
-     * Sets the {@code FileSystem} of the associated {@code FileHandler}.
+     * Sets the factory for creating {@code ReloadingDetector} objects. With
+     * this method a custom factory for reloading detectors can be installed.
+     * Per default, a factory creating {@code FileHandlerReloadingDetector}
+     * objects is used.
      *
-     * @param fs the {@code FileSystem}
+     * @param factory the {@code ReloadingDetectorFactory}
      * @return a reference to this object for method chaining
      */
-    T setFileSystem(FileSystem fs);
+    T setReloadingDetectorFactory(ReloadingDetectorFactory factory);
 
     /**
-     * Sets the {@code FileLocationStrategy} for resolving the referenced file.
+     * Sets the refresh delay for reloading support
      *
-     * @param strategy the {@code FileLocationStrategy}
+     * @param reloadingRefreshDelay the refresh delay (in milliseconds)
      * @return a reference to this object for method chaining
      */
-    T setLocationStrategy(FileLocationStrategy strategy);
+    T setReloadingRefreshDelay(Long reloadingRefreshDelay);
 
     /**
-     * Sets the encoding of the associated {@code FileHandler}.
+     * Sets the location of the associated {@code FileHandler} as a {@code URL}
+     * object.
      *
-     * @param enc the encoding
+     * @param url the {@code URL} location
      * @return a reference to this object for method chaining
      */
-    T setEncoding(String enc);
+    T setURL(URL url);
 }


[commons-configuration] 04/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit 95744ebbea5442859af869e8d7169317ef4592bc
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:47:09 2020 -0400

    Sort members.
---
 .../commons/configuration2/io/FileLocator.java     | 388 ++++++++++-----------
 1 file changed, 194 insertions(+), 194 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/io/FileLocator.java b/src/main/java/org/apache/commons/configuration2/io/FileLocator.java
index f0d02f7..bb7932c 100644
--- a/src/main/java/org/apache/commons/configuration2/io/FileLocator.java
+++ b/src/main/java/org/apache/commons/configuration2/io/FileLocator.java
@@ -56,6 +56,150 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
  */
 public final class FileLocator
 {
+    /**
+     * A typical <em>builder</em> implementation for creating
+     * {@code FileLocator} objects. An instance of this class is returned by the
+     * {@code fileLocator()} method of {link FileLocatorUtils}. It can be used
+     * to define the various components of the {@code FileLocator} object. By
+     * calling {@code create()} the new immutable {@code FileLocator} instance
+     * is created.
+     */
+    public static final class FileLocatorBuilder
+    {
+        /** The file name. */
+        private String fileName;
+
+        /** The base path. */
+        private String basePath;
+
+        /** The source URL. */
+        private URL sourceURL;
+
+        /** The encoding. */
+        private String encoding;
+
+        /** The file system. */
+        private FileSystem fileSystem;
+
+        /** The location strategy. */
+        private FileLocationStrategy locationStrategy;
+
+        /**
+         * Creates a new instance of {@code FileLocatorBuilder} and initializes
+         * the builder's properties from the passed in {@code FileLocator}
+         * object.
+         *
+         * @param src the source {@code FileLocator} (may be <b>null</b>)
+         */
+        FileLocatorBuilder(final FileLocator src)
+        {
+            if (src != null)
+            {
+                initBuilder(src);
+            }
+        }
+
+        /**
+         * Specifies the base path of the new {@code FileLocator}.
+         *
+         * @param path the base path
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder basePath(final String path)
+        {
+            basePath = path;
+            return this;
+        }
+
+        /**
+         * Creates a new immutable {@code FileLocatorImpl} object based on the
+         * properties set so far for this builder.
+         *
+         * @return the newly created {@code FileLocator} object
+         */
+        public FileLocator create()
+        {
+            return new FileLocator(this);
+        }
+
+        /**
+         * Specifies the encoding of the new {@code FileLocator}.
+         *
+         * @param enc the encoding
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder encoding(final String enc)
+        {
+            encoding = enc;
+            return this;
+        }
+
+        /**
+         * Specifies the file name of the new {@code FileLocator}.
+         *
+         * @param name the file name
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder fileName(final String name)
+        {
+            fileName = name;
+            return this;
+        }
+
+        /**
+         * Specifies the {@code FileSystem} of the new {@code FileLocator}.
+         *
+         * @param fs the {@code FileSystem}
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder fileSystem(final FileSystem fs)
+        {
+            fileSystem = fs;
+            return this;
+        }
+
+        /**
+         * Initializes the properties of this builder from the passed in locator
+         * object.
+         *
+         * @param src the source {@code FileLocator}
+         */
+        private void initBuilder(final FileLocator src)
+        {
+            basePath = src.getBasePath();
+            fileName = src.getFileName();
+            sourceURL = src.getSourceURL();
+            encoding = src.getEncoding();
+            fileSystem = src.getFileSystem();
+            locationStrategy = src.getLocationStrategy();
+        }
+
+        /**
+         * Specifies the {@code FileLocationStrategy} to be used when the
+         * referenced file is to be located.
+         *
+         * @param strategy the {@code FileLocationStrategy}
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder locationStrategy(final FileLocationStrategy strategy)
+        {
+            locationStrategy = strategy;
+            return this;
+        }
+
+        /**
+         * Specifies the source URL of the new {@code FileLocator}.
+         *
+         * @param url the source URL
+         * @return a reference to this builder for method chaining
+         */
+        public FileLocatorBuilder sourceURL(final URL url)
+        {
+            sourceURL = url;
+            return this;
+        }
+    }
+
     /** The file name. */
     private final String fileName;
 
@@ -91,14 +235,33 @@ public final class FileLocator
     }
 
     /**
-     * Returns the file name stored in this locator or <b>null</b> if it is
-     * undefined.
+     * Compares this object with another one. Two instances of
+     * {@code FileLocatorImpl} are considered equal if all of their properties
+     * are equal.
      *
-     * @return the file name
+     * @param obj the object to compare to
+     * @return a flag whether these objects are equal
      */
-    public String getFileName()
+    @Override
+    public boolean equals(final Object obj)
     {
-        return fileName;
+        if (this == obj)
+        {
+            return true;
+        }
+        if (!(obj instanceof FileLocator))
+        {
+            return false;
+        }
+
+        final FileLocator c = (FileLocator) obj;
+        return new EqualsBuilder().append(getFileName(), c.getFileName())
+                .append(getBasePath(), c.getBasePath())
+                .append(sourceURLAsString(), c.sourceURLAsString())
+                .append(getEncoding(), c.getEncoding())
+                .append(getFileSystem(), c.getFileSystem())
+                .append(getLocationStrategy(), c.getLocationStrategy())
+                .isEquals();
     }
 
     /**
@@ -113,25 +276,25 @@ public final class FileLocator
     }
 
     /**
-     * Returns the URL pointing to the referenced source file or <b>null</b> if
-     * it is undefined.
+     * Returns the encoding stored in this locator or <b>null</b> if it is
+     * undefined.
      *
-     * @return the source URL
+     * @return the encoding
      */
-    public URL getSourceURL()
+    public String getEncoding()
     {
-        return sourceURL;
+        return encoding;
     }
 
     /**
-     * Returns the encoding stored in this locator or <b>null</b> if it is
+     * Returns the file name stored in this locator or <b>null</b> if it is
      * undefined.
      *
-     * @return the encoding
+     * @return the file name
      */
-    public String getEncoding()
+    public String getFileName()
     {
-        return encoding;
+        return fileName;
     }
 
     /**
@@ -159,6 +322,17 @@ public final class FileLocator
     }
 
     /**
+     * Returns the URL pointing to the referenced source file or <b>null</b> if
+     * it is undefined.
+     *
+     * @return the source URL
+     */
+    public URL getSourceURL()
+    {
+        return sourceURL;
+    }
+
+    /**
      * Returns a hash code for this object.
      *
      * @return a hash code for this object
@@ -173,33 +347,15 @@ public final class FileLocator
     }
 
     /**
-     * Compares this object with another one. Two instances of
-     * {@code FileLocatorImpl} are considered equal if all of their properties
-     * are equal.
+     * Returns the source URL as a string. Result is never null. Comparisons are
+     * done on this string to avoid blocking network calls.
      *
-     * @param obj the object to compare to
-     * @return a flag whether these objects are equal
+     * @return the source URL as a string (not null)
      */
-    @Override
-    public boolean equals(final Object obj)
+    private String sourceURLAsString()
     {
-        if (this == obj)
-        {
-            return true;
-        }
-        if (!(obj instanceof FileLocator))
-        {
-            return false;
-        }
-
-        final FileLocator c = (FileLocator) obj;
-        return new EqualsBuilder().append(getFileName(), c.getFileName())
-                .append(getBasePath(), c.getBasePath())
-                .append(sourceURLAsString(), c.sourceURLAsString())
-                .append(getEncoding(), c.getEncoding())
-                .append(getFileSystem(), c.getFileSystem())
-                .append(getLocationStrategy(), c.getLocationStrategy())
-                .isEquals();
+        return sourceURL != null ? sourceURL.toExternalForm()
+                : StringUtils.EMPTY;
     }
 
     /**
@@ -218,160 +374,4 @@ public final class FileLocator
                 .append("fileSystem", getFileSystem())
                 .append("locationStrategy", getLocationStrategy()).toString();
     }
-
-    /**
-     * Returns the source URL as a string. Result is never null. Comparisons are
-     * done on this string to avoid blocking network calls.
-     *
-     * @return the source URL as a string (not null)
-     */
-    private String sourceURLAsString()
-    {
-        return sourceURL != null ? sourceURL.toExternalForm()
-                : StringUtils.EMPTY;
-    }
-
-    /**
-     * A typical <em>builder</em> implementation for creating
-     * {@code FileLocator} objects. An instance of this class is returned by the
-     * {@code fileLocator()} method of {link FileLocatorUtils}. It can be used
-     * to define the various components of the {@code FileLocator} object. By
-     * calling {@code create()} the new immutable {@code FileLocator} instance
-     * is created.
-     */
-    public static final class FileLocatorBuilder
-    {
-        /** The file name. */
-        private String fileName;
-
-        /** The base path. */
-        private String basePath;
-
-        /** The source URL. */
-        private URL sourceURL;
-
-        /** The encoding. */
-        private String encoding;
-
-        /** The file system. */
-        private FileSystem fileSystem;
-
-        /** The location strategy. */
-        private FileLocationStrategy locationStrategy;
-
-        /**
-         * Creates a new instance of {@code FileLocatorBuilder} and initializes
-         * the builder's properties from the passed in {@code FileLocator}
-         * object.
-         *
-         * @param src the source {@code FileLocator} (may be <b>null</b>)
-         */
-        FileLocatorBuilder(final FileLocator src)
-        {
-            if (src != null)
-            {
-                initBuilder(src);
-            }
-        }
-
-        /**
-         * Specifies the encoding of the new {@code FileLocator}.
-         *
-         * @param enc the encoding
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder encoding(final String enc)
-        {
-            encoding = enc;
-            return this;
-        }
-
-        /**
-         * Specifies the {@code FileSystem} of the new {@code FileLocator}.
-         *
-         * @param fs the {@code FileSystem}
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder fileSystem(final FileSystem fs)
-        {
-            fileSystem = fs;
-            return this;
-        }
-
-        /**
-         * Specifies the base path of the new {@code FileLocator}.
-         *
-         * @param path the base path
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder basePath(final String path)
-        {
-            basePath = path;
-            return this;
-        }
-
-        /**
-         * Specifies the file name of the new {@code FileLocator}.
-         *
-         * @param name the file name
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder fileName(final String name)
-        {
-            fileName = name;
-            return this;
-        }
-
-        /**
-         * Specifies the source URL of the new {@code FileLocator}.
-         *
-         * @param url the source URL
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder sourceURL(final URL url)
-        {
-            sourceURL = url;
-            return this;
-        }
-
-        /**
-         * Specifies the {@code FileLocationStrategy} to be used when the
-         * referenced file is to be located.
-         *
-         * @param strategy the {@code FileLocationStrategy}
-         * @return a reference to this builder for method chaining
-         */
-        public FileLocatorBuilder locationStrategy(final FileLocationStrategy strategy)
-        {
-            locationStrategy = strategy;
-            return this;
-        }
-
-        /**
-         * Creates a new immutable {@code FileLocatorImpl} object based on the
-         * properties set so far for this builder.
-         *
-         * @return the newly created {@code FileLocator} object
-         */
-        public FileLocator create()
-        {
-            return new FileLocator(this);
-        }
-
-        /**
-         * Initializes the properties of this builder from the passed in locator
-         * object.
-         *
-         * @param src the source {@code FileLocator}
-         */
-        private void initBuilder(final FileLocator src)
-        {
-            basePath = src.getBasePath();
-            fileName = src.getFileName();
-            sourceURL = src.getSourceURL();
-            encoding = src.getEncoding();
-            fileSystem = src.getFileSystem();
-            locationStrategy = src.getLocationStrategy();
-        }
-    }
 }


[commons-configuration] 06/06: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit cf1640dc906e5cc1b599a23ce130c6f00dcb7e16
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:59:52 2020 -0400

    Sort members.
---
 .../configuration2/io/FileLocatorUtils.java        | 522 ++++++++++-----------
 1 file changed, 261 insertions(+), 261 deletions(-)

diff --git a/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java b/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
index 5209b6d..e06b60f 100644
--- a/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
+++ b/src/main/java/org/apache/commons/configuration2/io/FileLocatorUtils.java
@@ -111,10 +111,123 @@ public final class FileLocatorUtils
     private static final String PROP_SOURCE_URL = "sourceURL";
 
     /**
-     * Private constructor so that no instances can be created.
+     * Extends a path by another component. The given extension is added to the
+     * already existing path adding a separator if necessary.
+     *
+     * @param path the path to be extended
+     * @param ext the extension of the path
+     * @return the extended path
      */
-    private FileLocatorUtils()
+    static String appendPath(final String path, final String ext)
+    {
+        final StringBuilder fName = new StringBuilder();
+        fName.append(path);
+
+        // My best friend. Paranoia.
+        if (!path.endsWith(File.separator))
+        {
+            fName.append(File.separator);
+        }
+
+        //
+        // We have a relative path, and we have
+        // two possible forms here. If we have the
+        // "./" form then just strip that off first
+        // before continuing.
+        //
+        if (ext.startsWith("." + File.separator))
+        {
+            fName.append(ext.substring(2));
+        }
+        else
+        {
+            fName.append(ext);
+        }
+        return fName.toString();
+    }
+
+    /**
+     * Helper method for constructing a file object from a base path and a
+     * file name. This method is called if the base path passed to
+     * {@code getURL()} does not seem to be a valid URL.
+     *
+     * @param basePath the base path
+     * @param fileName the file name (must not be <b>null</b>)
+     * @return the resulting file
+     */
+    static File constructFile(final String basePath, final String fileName)
+    {
+        File file;
+
+        final File absolute = new File(fileName);
+        if (StringUtils.isEmpty(basePath) || absolute.isAbsolute())
+        {
+            file = absolute;
+        }
+        else
+        {
+            file = new File(appendPath(basePath, fileName));
+        }
+
+        return file;
+    }
+
+    /**
+     * Tries to convert the specified file to a URL. If this causes an
+     * exception, result is <b>null</b>.
+     *
+     * @param file the file to be converted
+     * @return the resulting URL or <b>null</b>
+     */
+    static URL convertFileToURL(final File file)
+    {
+        return convertURIToURL(file.toURI());
+    }
+
+    /**
+     * Tries to convert the specified URI to a URL. If this causes an exception,
+     * result is <b>null</b>.
+     *
+     * @param uri the URI to be converted
+     * @return the resulting URL or <b>null</b>
+     */
+    static URL convertURIToURL(final URI uri)
+    {
+        try
+        {
+            return uri.toURL();
+        }
+        catch (final MalformedURLException e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a fully initialized {@code FileLocator} based on the specified
+     * URL.
+     *
+     * @param src the source {@code FileLocator}
+     * @param url the URL
+     * @return the fully initialized {@code FileLocator}
+     */
+    private static FileLocator createFullyInitializedLocatorFromURL(final FileLocator src,
+            final URL url)
     {
+        final FileLocator.FileLocatorBuilder fileLocatorBuilder = fileLocator(src);
+        if (src.getSourceURL() == null)
+        {
+            fileLocatorBuilder.sourceURL(url);
+        }
+        if (StringUtils.isBlank(src.getFileName()))
+        {
+            fileLocatorBuilder.fileName(getFileName(url));
+        }
+        if (StringUtils.isBlank(src.getBasePath()))
+        {
+            fileLocatorBuilder.basePath(getBasePath(url));
+        }
+        return fileLocatorBuilder.create();
     }
 
     /**
@@ -195,79 +308,6 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Stores the specified {@code FileLocator} in the given map. With the
-     * {@link #fromMap(Map)} method a new {@code FileLocator} with the same
-     * properties as the original one can be created.
-     *
-     * @param locator the {@code FileLocator} to be stored
-     * @param map the map in which to store the {@code FileLocator} (must not be
-     *        <b>null</b>)
-     * @throws IllegalArgumentException if the map is <b>null</b>
-     */
-    public static void put(final FileLocator locator, final Map<String, Object> map)
-    {
-        if (map == null)
-        {
-            throw new IllegalArgumentException("Map must not be null!");
-        }
-
-        if (locator != null)
-        {
-            map.put(PROP_BASE_PATH, locator.getBasePath());
-            map.put(PROP_ENCODING, locator.getEncoding());
-            map.put(PROP_FILE_NAME, locator.getFileName());
-            map.put(PROP_FILE_SYSTEM, locator.getFileSystem());
-            map.put(PROP_SOURCE_URL, locator.getSourceURL());
-            map.put(PROP_STRATEGY, locator.getLocationStrategy());
-        }
-    }
-
-    /**
-     * Checks whether the specified {@code FileLocator} contains enough
-     * information to locate a file. This is the case if a file name or a URL is
-     * defined. If the passed in {@code FileLocator} is <b>null</b>, result is
-     * <b>false</b>.
-     *
-     * @param locator the {@code FileLocator} to check
-     * @return a flag whether a file location is defined by this
-     *         {@code FileLocator}
-     */
-    public static boolean isLocationDefined(final FileLocator locator)
-    {
-        return locator != null
-                && (locator.getFileName() != null || locator.getSourceURL() != null);
-    }
-
-    /**
-     * Returns a flag whether all components of the given {@code FileLocator}
-     * describing the referenced file are defined. In order to reference a file,
-     * it is not necessary that all components are filled in (for instance, the
-     * URL alone is sufficient). For some use cases however, it might be of
-     * interest to have different methods for accessing the referenced file.
-     * Also, depending on the filled out properties, there is a subtle
-     * difference how the file is accessed: If only the file name is set (and
-     * optionally the base path), each time the file is accessed a
-     * {@code locate()} operation has to be performed to uniquely identify the
-     * file. If however the URL is determined once based on the other components
-     * and stored in a fully defined {@code FileLocator}, it can be used
-     * directly to identify the file. If the passed in {@code FileLocator} is
-     * <b>null</b>, result is <b>false</b>.
-     *
-     * @param locator the {@code FileLocator} to be checked (may be <b>null</b>)
-     * @return a flag whether all components describing the referenced file are
-     *         initialized
-     */
-    public static boolean isFullyInitialized(final FileLocator locator)
-    {
-        if (locator == null)
-        {
-            return false;
-        }
-        return locator.getBasePath() != null && locator.getFileName() != null
-                && locator.getSourceURL() != null;
-    }
-
-    /**
      * Returns a {@code FileLocator} object based on the passed in one whose
      * location is fully defined. This method ensures that all components of the
      * {@code FileLocator} pointing to the file are set in a consistent way. In
@@ -301,53 +341,6 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Locates the provided {@code FileLocator}, returning a URL for accessing
-     * the referenced file. This method uses a {@link FileLocationStrategy} to
-     * locate the file the passed in {@code FileLocator} points to. If the
-     * {@code FileLocator} contains itself a {@code FileLocationStrategy}, it is
-     * used. Otherwise, the default {@code FileLocationStrategy} is applied. The
-     * strategy is passed the locator and a {@code FileSystem}. The resulting
-     * URL is returned. If the {@code FileLocator} is <b>null</b>, result is
-     * <b>null</b>.
-     *
-     * @param locator the {@code FileLocator} to be resolved
-     * @return the URL pointing to the referenced file or <b>null</b> if the
-     *         {@code FileLocator} could not be resolved
-     * @see #DEFAULT_LOCATION_STRATEGY
-     */
-    public static URL locate(final FileLocator locator)
-    {
-        if (locator == null)
-        {
-            return null;
-        }
-
-        return obtainLocationStrategy(locator).locate(
-                obtainFileSystem(locator), locator);
-    }
-
-    /**
-     * Tries to locate the file referenced by the passed in {@code FileLocator}.
-     * If this fails, an exception is thrown. This method works like
-     * {@link #locate(FileLocator)}; however, in case of a failed location
-     * attempt an exception is thrown.
-     *
-     * @param locator the {@code FileLocator} to be resolved
-     * @return the URL pointing to the referenced file
-     * @throws ConfigurationException if the file cannot be resolved
-     */
-    public static URL locateOrThrow(final FileLocator locator)
-            throws ConfigurationException
-    {
-        final URL url = locate(locator);
-        if (url == null)
-        {
-            throw new ConfigurationException("Could not locate: " + locator);
-        }
-        return url;
-    }
-
-    /**
      * Return the path without the file name, for example http://xyz.net/foo/bar.xml
      * results in http://xyz.net/foo/
      *
@@ -375,28 +368,6 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Extract the file name from the specified URL.
-     *
-     * @param url the URL from which to extract the file name
-     * @return the extracted file name
-     */
-    static String getFileName(final URL url)
-    {
-        if (url == null)
-        {
-            return null;
-        }
-
-        final String path = url.getPath();
-
-        if (path.endsWith("/") || StringUtils.isEmpty(path))
-        {
-            return null;
-        }
-        return path.substring(path.lastIndexOf("/") + 1);
-    }
-
-    /**
      * Tries to convert the specified base path and file name into a file object.
      * This method is called e.g. by the save() methods of file based
      * configurations. The parameter strings can be relative files, absolute
@@ -457,53 +428,118 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Convert the specified file into an URL. This method is equivalent
-     * to file.toURI().toURL(). It was used to work around a bug in the JDK
-     * preventing the transformation of a file into an URL if the file name
-     * contains a '#' character. See the issue CONFIGURATION-300 for
-     * more details. Now that we switched to JDK 1.4 we can directly use
-     * file.toURI().toURL().
+     * Extract the file name from the specified URL.
      *
-     * @param file the file to be converted into an URL
-     * @return a URL
-     * @throws  MalformedURLException
-     *          If the file protocol handler is not found (should not happen)
-     *          or if an error occurred while constructing the URL
+     * @param url the URL from which to extract the file name
+     * @return the extracted file name
      */
-    static URL toURL(final File file) throws MalformedURLException
+    static String getFileName(final URL url)
     {
-        return file.toURI().toURL();
+        if (url == null)
+        {
+            return null;
+        }
+
+        final String path = url.getPath();
+
+        if (path.endsWith("/") || StringUtils.isEmpty(path))
+        {
+            return null;
+        }
+        return path.substring(path.lastIndexOf("/") + 1);
     }
 
     /**
-     * Tries to convert the specified URI to a URL. If this causes an exception,
-     * result is <b>null</b>.
+     * Creates the default location strategy. This method creates a combined
+     * location strategy as described in the comment of the
+     * {@link #DEFAULT_LOCATION_STRATEGY} member field.
      *
-     * @param uri the URI to be converted
-     * @return the resulting URL or <b>null</b>
+     * @return the default {@code FileLocationStrategy}
      */
-    static URL convertURIToURL(final URI uri)
+    private static FileLocationStrategy initDefaultLocationStrategy()
     {
-        try
-        {
-            return uri.toURL();
-        }
-        catch (final MalformedURLException e)
+        final FileLocationStrategy[] subStrategies =
+                new FileLocationStrategy[] {
+                        new ProvidedURLLocationStrategy(),
+                        new FileSystemLocationStrategy(),
+                        new AbsoluteNameLocationStrategy(),
+                        new BasePathLocationStrategy(),
+                        new HomeDirectoryLocationStrategy(true),
+                        new HomeDirectoryLocationStrategy(false),
+                        new ClasspathLocationStrategy()
+                };
+        return new CombinedLocationStrategy(Arrays.asList(subStrategies));
+    }
+
+    /**
+     * Returns a flag whether all components of the given {@code FileLocator}
+     * describing the referenced file are defined. In order to reference a file,
+     * it is not necessary that all components are filled in (for instance, the
+     * URL alone is sufficient). For some use cases however, it might be of
+     * interest to have different methods for accessing the referenced file.
+     * Also, depending on the filled out properties, there is a subtle
+     * difference how the file is accessed: If only the file name is set (and
+     * optionally the base path), each time the file is accessed a
+     * {@code locate()} operation has to be performed to uniquely identify the
+     * file. If however the URL is determined once based on the other components
+     * and stored in a fully defined {@code FileLocator}, it can be used
+     * directly to identify the file. If the passed in {@code FileLocator} is
+     * <b>null</b>, result is <b>false</b>.
+     *
+     * @param locator the {@code FileLocator} to be checked (may be <b>null</b>)
+     * @return a flag whether all components describing the referenced file are
+     *         initialized
+     */
+    public static boolean isFullyInitialized(final FileLocator locator)
+    {
+        if (locator == null)
         {
-            return null;
+            return false;
         }
+        return locator.getBasePath() != null && locator.getFileName() != null
+                && locator.getSourceURL() != null;
     }
 
     /**
-     * Tries to convert the specified file to a URL. If this causes an
-     * exception, result is <b>null</b>.
+     * Checks whether the specified {@code FileLocator} contains enough
+     * information to locate a file. This is the case if a file name or a URL is
+     * defined. If the passed in {@code FileLocator} is <b>null</b>, result is
+     * <b>false</b>.
      *
-     * @param file the file to be converted
-     * @return the resulting URL or <b>null</b>
+     * @param locator the {@code FileLocator} to check
+     * @return a flag whether a file location is defined by this
+     *         {@code FileLocator}
      */
-    static URL convertFileToURL(final File file)
+    public static boolean isLocationDefined(final FileLocator locator)
     {
-        return convertURIToURL(file.toURI());
+        return locator != null
+                && (locator.getFileName() != null || locator.getSourceURL() != null);
+    }
+
+    /**
+     * Locates the provided {@code FileLocator}, returning a URL for accessing
+     * the referenced file. This method uses a {@link FileLocationStrategy} to
+     * locate the file the passed in {@code FileLocator} points to. If the
+     * {@code FileLocator} contains itself a {@code FileLocationStrategy}, it is
+     * used. Otherwise, the default {@code FileLocationStrategy} is applied. The
+     * strategy is passed the locator and a {@code FileSystem}. The resulting
+     * URL is returned. If the {@code FileLocator} is <b>null</b>, result is
+     * <b>null</b>.
+     *
+     * @param locator the {@code FileLocator} to be resolved
+     * @return the URL pointing to the referenced file or <b>null</b> if the
+     *         {@code FileLocator} could not be resolved
+     * @see #DEFAULT_LOCATION_STRATEGY
+     */
+    public static URL locate(final FileLocator locator)
+    {
+        if (locator == null)
+        {
+            return null;
+        }
+
+        return obtainLocationStrategy(locator).locate(
+                obtainFileSystem(locator), locator);
     }
 
     /**
@@ -542,65 +578,24 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Helper method for constructing a file object from a base path and a
-     * file name. This method is called if the base path passed to
-     * {@code getURL()} does not seem to be a valid URL.
-     *
-     * @param basePath the base path
-     * @param fileName the file name (must not be <b>null</b>)
-     * @return the resulting file
-     */
-    static File constructFile(final String basePath, final String fileName)
-    {
-        File file;
-
-        final File absolute = new File(fileName);
-        if (StringUtils.isEmpty(basePath) || absolute.isAbsolute())
-        {
-            file = absolute;
-        }
-        else
-        {
-            file = new File(appendPath(basePath, fileName));
-        }
-
-        return file;
-    }
-
-    /**
-     * Extends a path by another component. The given extension is added to the
-     * already existing path adding a separator if necessary.
+     * Tries to locate the file referenced by the passed in {@code FileLocator}.
+     * If this fails, an exception is thrown. This method works like
+     * {@link #locate(FileLocator)}; however, in case of a failed location
+     * attempt an exception is thrown.
      *
-     * @param path the path to be extended
-     * @param ext the extension of the path
-     * @return the extended path
+     * @param locator the {@code FileLocator} to be resolved
+     * @return the URL pointing to the referenced file
+     * @throws ConfigurationException if the file cannot be resolved
      */
-    static String appendPath(final String path, final String ext)
+    public static URL locateOrThrow(final FileLocator locator)
+            throws ConfigurationException
     {
-        final StringBuilder fName = new StringBuilder();
-        fName.append(path);
-
-        // My best friend. Paranoia.
-        if (!path.endsWith(File.separator))
-        {
-            fName.append(File.separator);
-        }
-
-        //
-        // We have a relative path, and we have
-        // two possible forms here. If we have the
-        // "./" form then just strip that off first
-        // before continuing.
-        //
-        if (ext.startsWith("." + File.separator))
-        {
-            fName.append(ext.substring(2));
-        }
-        else
+        final URL url = locate(locator);
+        if (url == null)
         {
-            fName.append(ext);
+            throw new ConfigurationException("Could not locate: " + locator);
         }
-        return fName.toString();
+        return url;
     }
 
     /**
@@ -637,51 +632,56 @@ public final class FileLocatorUtils
     }
 
     /**
-     * Creates a fully initialized {@code FileLocator} based on the specified
-     * URL.
+     * Stores the specified {@code FileLocator} in the given map. With the
+     * {@link #fromMap(Map)} method a new {@code FileLocator} with the same
+     * properties as the original one can be created.
      *
-     * @param src the source {@code FileLocator}
-     * @param url the URL
-     * @return the fully initialized {@code FileLocator}
+     * @param locator the {@code FileLocator} to be stored
+     * @param map the map in which to store the {@code FileLocator} (must not be
+     *        <b>null</b>)
+     * @throws IllegalArgumentException if the map is <b>null</b>
      */
-    private static FileLocator createFullyInitializedLocatorFromURL(final FileLocator src,
-            final URL url)
+    public static void put(final FileLocator locator, final Map<String, Object> map)
     {
-        final FileLocator.FileLocatorBuilder fileLocatorBuilder = fileLocator(src);
-        if (src.getSourceURL() == null)
-        {
-            fileLocatorBuilder.sourceURL(url);
-        }
-        if (StringUtils.isBlank(src.getFileName()))
+        if (map == null)
         {
-            fileLocatorBuilder.fileName(getFileName(url));
+            throw new IllegalArgumentException("Map must not be null!");
         }
-        if (StringUtils.isBlank(src.getBasePath()))
+
+        if (locator != null)
         {
-            fileLocatorBuilder.basePath(getBasePath(url));
+            map.put(PROP_BASE_PATH, locator.getBasePath());
+            map.put(PROP_ENCODING, locator.getEncoding());
+            map.put(PROP_FILE_NAME, locator.getFileName());
+            map.put(PROP_FILE_SYSTEM, locator.getFileSystem());
+            map.put(PROP_SOURCE_URL, locator.getSourceURL());
+            map.put(PROP_STRATEGY, locator.getLocationStrategy());
         }
-        return fileLocatorBuilder.create();
     }
 
     /**
-     * Creates the default location strategy. This method creates a combined
-     * location strategy as described in the comment of the
-     * {@link #DEFAULT_LOCATION_STRATEGY} member field.
+     * Convert the specified file into an URL. This method is equivalent
+     * to file.toURI().toURL(). It was used to work around a bug in the JDK
+     * preventing the transformation of a file into an URL if the file name
+     * contains a '#' character. See the issue CONFIGURATION-300 for
+     * more details. Now that we switched to JDK 1.4 we can directly use
+     * file.toURI().toURL().
      *
-     * @return the default {@code FileLocationStrategy}
+     * @param file the file to be converted into an URL
+     * @return a URL
+     * @throws  MalformedURLException
+     *          If the file protocol handler is not found (should not happen)
+     *          or if an error occurred while constructing the URL
      */
-    private static FileLocationStrategy initDefaultLocationStrategy()
+    static URL toURL(final File file) throws MalformedURLException
+    {
+        return file.toURI().toURL();
+    }
+
+    /**
+     * Private constructor so that no instances can be created.
+     */
+    private FileLocatorUtils()
     {
-        final FileLocationStrategy[] subStrategies =
-                new FileLocationStrategy[] {
-                        new ProvidedURLLocationStrategy(),
-                        new FileSystemLocationStrategy(),
-                        new AbsoluteNameLocationStrategy(),
-                        new BasePathLocationStrategy(),
-                        new HomeDirectoryLocationStrategy(true),
-                        new HomeDirectoryLocationStrategy(false),
-                        new ClasspathLocationStrategy()
-                };
-        return new CombinedLocationStrategy(Arrays.asList(subStrategies));
     }
 }