You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2012/09/28 19:54:34 UTC

svn commit: r1391574 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/ main/java/org/apache/commons/configuration/io/ test/java/org/apache/commons/configuration/io/

Author: oheger
Date: Fri Sep 28 17:54:34 2012
New Revision: 1391574

URL: http://svn.apache.org/viewvc?rev=1391574&view=rev
Log:
Started rework of file-based configurations:
- Added new FileBased interface
- Created FileHandler class which manages a location and a FileBased object

Added:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java   (with props)
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java   (with props)
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java   (with props)
Modified:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java

Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java?rev=1391574&r1=1391573&r2=1391574&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java (original)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java Fri Sep 28 17:54:34 2012
@@ -571,7 +571,7 @@ public final class ConfigurationUtils
      * @param url the URL from which to extract the path
      * @return the path component of the passed in URL
      */
-    static String getBasePath(URL url)
+    public static String getBasePath(URL url)
     {
         if (url == null)
         {
@@ -600,7 +600,7 @@ public final class ConfigurationUtils
      * @param url the URL from which to extract the file name
      * @return the extracted file name
      */
-    static String getFileName(URL url)
+    public static String getFileName(URL url)
     {
         if (url == null)
         {
@@ -721,7 +721,7 @@ public final class ConfigurationUtils
      *
      * @param file the file to be converted into an URL
      */
-    static URL toURL(File file) throws MalformedURLException
+    public static URL toURL(File file) throws MalformedURLException
     {
         return file.toURI().toURL();
     }

Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java?rev=1391574&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java Fri Sep 28 17:54:34 2012
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.io;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+/**
+ * <p>
+ * Definition of an interface to be implemented by objects which know how to
+ * read and write themselves from or to a character stream.
+ * </p>
+ * <p>
+ * This interface is implemented by special implementations of the
+ * {@code Configuration} interface which are associated with a file. It demands
+ * only basic methods for doing I/O based on character stream objects. Based on
+ * these methods it is possible to implement other methods which operate on
+ * files, file names, URLs, etc.
+ * </p>
+ *
+ * @version $Id$
+ */
+public interface FileBased
+{
+    /**
+     * Reads the content of this object from the given reader.
+     *
+     * @param in the reader
+     * @throws IOException if an I/O error occurs
+     * @throws ConfigurationException if a non-I/O related problem occurs, e.g.
+     *         the data read does not have the expected format
+     */
+    void read(Reader in) throws ConfigurationException, IOException;
+
+    /**
+     * Writes the content of this object to the given writer.
+     *
+     * @param out the writer
+     * @throws IOException if an I/O error occurs
+     * @throws ConfigurationException if a non-I/O related problem occurs, e.g.
+     *         the data read does not have the expected format
+     */
+    void write(Writer out) throws ConfigurationException, IOException;
+}

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileBased.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java?rev=1391574&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java Fri Sep 28 17:54:34 2012
@@ -0,0 +1,1056 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.io;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.ConfigurationUtils;
+import org.apache.commons.configuration.FileSystem;
+
+/**
+ * <p>
+ * A class that manages persistence of an associated {@link FileBased} object.
+ * </p>
+ * <p>
+ * Instances of this class can be used to load and save arbitrary objects
+ * implementing the {@code FileBased} interface in a convenient way from and to
+ * various locations. At construction time the {@code FileBased} object to
+ * manage is passed in. Basically, this object is assigned a location from which
+ * it is loaded and to which it can be saved. The following possibilities exist
+ * to specify such a location:
+ * <ul>
+ * <li>URLs: With the method {@code setURL()} a full URL to the configuration
+ * source can be specified. This is the most flexible way. Note that the
+ * {@code save()} methods support only <em>file:</em> URLs.</li>
+ * <li>Files: The {@code setFile()} method allows to specify the configuration
+ * source as a file. This can be either a relative or an absolute file. In the
+ * former case the file is resolved based on the current directory.</li>
+ * <li>As file paths in string form: With the {@code setPath()} method a full
+ * path to a configuration file can be provided as a string.</li>
+ * <li>Separated as base path and file name: This is the native form in which
+ * the location is stored. The base path is a string defining either a local
+ * directory or a URL. It can be set using the {@code setBasePath()} method. The
+ * file name, non surprisingly, defines the name of the configuration file.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * An instance stores a location. The {@code load()} and {@code save()} methods
+ * that do not take an argument make use of this internal location.
+ * Alternatively, it is also possible to use overloaded variants of
+ * {@code load()} and {@code save()} which expect a location. In these cases the
+ * location specified takes precedence over the internal one; the internal
+ * location is not changed.
+ * </p>
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @version $Id$
+ */
+public class FileHandler
+{
+    /** Constant for the URI scheme for files. */
+    private static final String FILE_SCHEME = "file:";
+
+    /** Constant for the URI scheme for files with slashes. */
+    private static final String FILE_SCHEME_SLASH = FILE_SCHEME + "//";
+
+    /** The file-based object managed by this handler. */
+    private final FileBased content;
+
+    /** Stores the location of the associated file. */
+    private final FileSpec fileSpec;
+
+    /**
+     * Creates a new instance of {@code FileHandler} which is not associated
+     * with a {@code FileBased} object and thus does not have a content. Objects
+     * of this kind can be used to define a file location, but it is not
+     * possible to actually load or save data.
+     */
+    public FileHandler()
+    {
+        this(null);
+    }
+
+    /**
+     * Creates a new instance of {@code FileHandler} and sets the managed
+     * {@code FileBased} object.
+     *
+     * @param obj the file-based object to manage
+     */
+    public FileHandler(FileBased obj)
+    {
+        content = obj;
+        fileSpec = new FileSpec();
+    }
+
+    /**
+     * Creates a new instance of {@code FileHandler} which is associated with
+     * the given {@code FileBased} object and the location defined for the given
+     * {@code FileHandler} object. A copy of the location of the given
+     * {@code FileHandler} is created. This constructor is a possibility to
+     * associate a file location with a {@code FileBased} object.
+     *
+     * @param obj the {@code FileBased} object to manage
+     * @param c the {@code FileHandler} from which to copy the location (must
+     *        not be <b>null</b>)
+     * @throws IllegalArgumentException if the {@code FileHandler} is
+     *         <b>null</b>
+     */
+    public FileHandler(FileBased obj, FileHandler c)
+    {
+        content = obj;
+        fileSpec = c.snapshotFileSpec();
+    }
+
+    /**
+     * Returns the {@code FileBased} object associated with this
+     * {@code FileHandler}.
+     *
+     * @return the associated {@code FileBased} object
+     */
+    public final FileBased getContent()
+    {
+        return content;
+    }
+
+    /**
+     * Return the name of the file.
+     *
+     * @return the file name
+     */
+    public String getFileName()
+    {
+        synchronized (fileSpec)
+        {
+            return fileSpec.getFileName();
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param fileName the name of the file
+     */
+    public void setFileName(String fileName)
+    {
+        String name = normalizeFileURL(fileName);
+        synchronized (fileSpec)
+        {
+            fileSpec.setFileName(name);
+            fileSpec.setSourceURL(null);
+        }
+    }
+
+    /**
+     * Return the base path.
+     *
+     * @return the base path
+     */
+    public String getBasePath()
+    {
+        synchronized (fileSpec)
+        {
+            return fileSpec.getBasePath();
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param basePath the base path.
+     */
+    public void setBasePath(String basePath)
+    {
+        String path = normalizeFileURL(basePath);
+        synchronized (fileSpec)
+        {
+            fileSpec.setBasePath(path);
+            fileSpec.setSourceURL(null);
+        }
+    }
+
+    /**
+     * 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>
+     */
+    public File getFile()
+    {
+        String fileName;
+        String basePath;
+        URL sourceURL;
+
+        synchronized (fileSpec)
+        {
+            fileName = fileSpec.getFileName();
+            basePath = fileSpec.getBasePath();
+            sourceURL = fileSpec.getSourceURL();
+        }
+
+        if (fileName == null && sourceURL == null)
+        {
+            return null;
+        }
+        else if (sourceURL != null)
+        {
+            return ConfigurationUtils.fileFromURL(sourceURL);
+        }
+        else
+        {
+            return ConfigurationUtils.getFile(basePath, fileName);
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void setFile(File file)
+    {
+        synchronized (fileSpec)
+        {
+            fileSpec.setFileName(file.getName());
+            fileSpec.setBasePath((file.getParentFile() != null) ? file
+                    .getParentFile().getAbsolutePath() : null);
+            fileSpec.setSourceURL(null);
+        }
+    }
+
+    /**
+     * 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 full path to the associated file
+     */
+    public String getPath()
+    {
+        FileSpec spec;
+        File file;
+        synchronized (fileSpec)
+        {
+            spec = snapshotFileSpec();
+            file = getFile();
+        }
+
+        return spec.getFileSystem().getPath(file, spec.getSourceURL(),
+                spec.getBasePath(), spec.getFileName());
+    }
+
+    /**
+     * 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.
+     *
+     * @param path the full path name of the associated file
+     */
+    public void setPath(String path)
+    {
+        setFile(new File(path));
+    }
+
+    /**
+     * Returns the location of the associated file as a URL.
+     *
+     * @return a URL to the associated file; can be <b>null</b> if the location
+     *         is unspecified
+     */
+    public URL getURL()
+    {
+        URL sourceURL;
+        FileSystem fileSystem;
+        String basePath;
+        String fileName;
+        synchronized (fileSpec)
+        {
+            sourceURL = fileSpec.getSourceURL();
+            fileSystem = fileSpec.getFileSystem();
+            basePath = fileSpec.getBasePath();
+            fileName = fileSpec.getFileName();
+        }
+
+        return (sourceURL != null) ? sourceURL : ConfigurationUtils.locate(
+                fileSystem, basePath, fileName);
+    }
+
+    /**
+     * 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.
+     *
+     * @param url the location of the file as URL
+     */
+    public void setURL(URL url)
+    {
+        String basePath = ConfigurationUtils.getBasePath(url);
+        String fileName = ConfigurationUtils.getFileName(url);
+        synchronized (fileSpec)
+        {
+            fileSpec.setBasePath(basePath);
+            fileSpec.setFileName(fileName);
+            fileSpec.setSourceURL(url);
+        }
+    }
+
+    /**
+     * Returns the encoding of the associated file. Result can be <b>null</b> if
+     * no encoding has been set.
+     *
+     * @return the encoding of the associated file
+     */
+    public String getEncoding()
+    {
+        synchronized (fileSpec)
+        {
+            return fileSpec.getEncoding();
+        }
+    }
+
+    /**
+     * Sets the encoding of the associated file. The encoding applies if binary
+     * files are loaded.
+     *
+     * @param encoding the encoding of the associated file
+     */
+    public void setEncoding(String encoding)
+    {
+        synchronized (fileSpec)
+        {
+            fileSpec.setEncoding(encoding);
+        }
+    }
+
+    /**
+     * 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()
+    {
+        synchronized (fileSpec)
+        {
+            return fileSpec.getFileSystem();
+        }
+    }
+
+    /**
+     * 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 fileSystem the {@code FileSystem}
+     */
+    public void setFileSystem(FileSystem fileSystem)
+    {
+        FileSystem fs =
+                (fileSystem != null) ? fileSystem : FileSystem
+                        .getDefaultFileSystem();
+        synchronized (fileSpec)
+        {
+            fileSpec.setFileSystem(fs);
+        }
+    }
+
+    /**
+     * Resets the {@code FileSystem} used by this object. It is set to the
+     * default file system.
+     */
+    public void resetFileSystem()
+    {
+        setFileSystem(null);
+    }
+
+    /**
+     * Loads the associated file from the underlying location. If no location
+     * has been set, an exception is thrown.
+     *
+     * @throws ConfigurationException if loading of the configuration fails
+     */
+    public void load() throws ConfigurationException
+    {
+        load(checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(String fileName) throws ConfigurationException
+    {
+        load(fileName, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * Loads the associated file from the specified {@code File}.
+     *
+     * @param file the file to load
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(File file) throws ConfigurationException
+    {
+        URL url;
+        try
+        {
+            url = ConfigurationUtils.toURL(file);
+        }
+        catch (MalformedURLException e1)
+        {
+            throw new ConfigurationException("Cannot create URL from file "
+                    + file);
+        }
+
+        load(url);
+    }
+
+    /**
+     * Loads the associated file from the specified URL. The location stored in
+     * this object is not changed.
+     *
+     * @param url the URL of the file to be loaded
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(URL url) throws ConfigurationException
+    {
+        load(url, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * Loads the associated file from the specified stream, using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param in the input stream
+     * @throws ConfigurationException if an error occurs during the load
+     *         operation
+     */
+    public void load(InputStream in) throws ConfigurationException
+    {
+        load(in, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * Loads the associated file from the specified stream, using the specified
+     * encoding. If the encoding is <b>null</b>, the default encoding is used.
+     *
+     * @param in the input stream
+     * @param encoding the encoding used, {@code null} to use the default
+     *        encoding
+     * @throws ConfigurationException if an error occurs during the load
+     *         operation
+     */
+    public void load(InputStream in, String encoding)
+            throws ConfigurationException
+    {
+        checkContent();
+        Reader reader = null;
+
+        if (encoding != null)
+        {
+            try
+            {
+                reader = new InputStreamReader(in, encoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new ConfigurationException(
+                        "The requested encoding is not supported, try the default encoding.",
+                        e);
+            }
+        }
+
+        if (reader == null)
+        {
+            reader = new InputStreamReader(in);
+        }
+
+        loadFromReader(reader);
+    }
+
+    /**
+     * Loads the associated file from the specified reader.
+     *
+     * @param in the reader
+     * @throws ConfigurationException if an error occurs during the load
+     *         operation
+     */
+    public void load(Reader in) throws ConfigurationException
+    {
+        checkContent();
+        loadFromReader(in);
+    }
+
+    /**
+     * 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(checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(String fileName) throws ConfigurationException
+    {
+        save(fileName, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(URL url) throws ConfigurationException
+    {
+        save(url, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(File file) throws ConfigurationException
+    {
+        save(file, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(OutputStream out) throws ConfigurationException
+    {
+        save(out, checkContentAndCreateSnapshotFileSpec());
+    }
+
+    /**
+     * 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(OutputStream out, String encoding)
+            throws ConfigurationException
+    {
+        checkContent();
+        Writer writer = null;
+
+        if (encoding != null)
+        {
+            try
+            {
+                writer = new OutputStreamWriter(out, encoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new ConfigurationException(
+                        "The requested encoding is not supported, try the default encoding.",
+                        e);
+            }
+        }
+
+        if (writer == null)
+        {
+            writer = new OutputStreamWriter(out);
+        }
+
+        saveToWriter(writer);
+    }
+
+    /**
+     * 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(Writer out) throws ConfigurationException
+    {
+        checkContent();
+        saveToWriter(out);
+    }
+
+    /**
+     * Internal helper method for loading the associated file from the location
+     * specified in the given {@code FileSpec}.
+     *
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(FileSpec spec) throws ConfigurationException
+    {
+        if (spec.getSourceURL() != null)
+        {
+            load(spec.getSourceURL(), spec);
+        }
+        else
+        {
+            load(spec.getFileName(), spec);
+        }
+    }
+
+    /**
+     * Internal helper method for loading a file from the given URL.
+     *
+     * @param url the URL
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(URL url, FileSpec spec) throws ConfigurationException
+    {
+        InputStream in = null;
+
+        try
+        {
+            in = spec.getFileSystem().getInputStream(url);
+            load(in, spec);
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException(
+                    "Unable to load the configuration from the URL " + url, e);
+        }
+        finally
+        {
+            closeSilent(in);
+        }
+    }
+
+    /**
+     * Internal helper method for loading a file from a file name.
+     *
+     * @param fileName the file name
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(String fileName, FileSpec spec)
+            throws ConfigurationException
+    {
+        URL url =
+                ConfigurationUtils.locate(spec.getFileSystem(),
+                        spec.getBasePath(), fileName);
+
+        if (url == null)
+        {
+            throw new ConfigurationException(
+                    "Cannot locate configuration source " + fileName);
+        }
+        load(url);
+    }
+
+    /**
+     * Internal helper method for loading a file from the given input stream.
+     *
+     * @param in the input stream
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(InputStream in, FileSpec spec)
+            throws ConfigurationException
+    {
+        load(in, spec.getEncoding());
+    }
+
+    /**
+     * Internal helper method for loading a file from the given reader.
+     *
+     * @param in the reader
+     * @throws ConfigurationException if an error occurs
+     */
+    private void loadFromReader(Reader in) throws ConfigurationException
+    {
+        try
+        {
+            getContent().read(in);
+        }
+        catch (IOException ioex)
+        {
+            throw new ConfigurationException(ioex);
+        }
+    }
+
+    /**
+     * Internal helper method for saving data to the internal location stored
+     * for this object.
+     *
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(FileSpec spec) throws ConfigurationException
+    {
+        if (spec.getFileName() == null)
+        {
+            throw new ConfigurationException("No file name has been set!");
+        }
+
+        if (spec.getSourceURL() != null)
+        {
+            save(spec.getSourceURL(), spec);
+        }
+        else
+        {
+            save(spec.getFileName(), spec);
+        }
+    }
+
+    /**
+     * Internal helper method for saving data to the given file name.
+     *
+     * @param fileName the path to the target file
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(String fileName, FileSpec spec)
+            throws ConfigurationException
+    {
+        URL url;
+        try
+        {
+            url = spec.getFileSystem().getURL(spec.getBasePath(), fileName);
+        }
+        catch (MalformedURLException e)
+        {
+            throw new ConfigurationException(e);
+        }
+
+        if (url == null)
+        {
+            throw new ConfigurationException(
+                    "Cannot locate configuration source " + fileName);
+        }
+        save(url, spec);
+    }
+
+    /**
+     * Internal helper method for saving data to the given URL.
+     *
+     * @param url the target URL
+     * @param spec the {@code FileSpec}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(URL url, FileSpec spec) throws ConfigurationException
+    {
+        OutputStream out = null;
+        try
+        {
+            out = spec.getFileSystem().getOutputStream(url);
+            save(out, spec);
+        }
+        finally
+        {
+            closeSilent(out);
+        }
+    }
+
+    /**
+     * Internal helper method for saving data to the given {@code File}.
+     *
+     * @param file the target file
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(File file, FileSpec spec) throws ConfigurationException
+    {
+        OutputStream out = null;
+
+        try
+        {
+            out = spec.getFileSystem().getOutputStream(file);
+            save(out, spec);
+        }
+        finally
+        {
+            closeSilent(out);
+        }
+    }
+
+    /**
+     * Internal helper method for saving a file to the given output stream.
+     *
+     * @param out the output stream
+     * @param spec the current {@code FileSpec}
+     * @throws ConfigurationException if an error occurs during the save
+     *         operation
+     */
+    private void save(OutputStream out, FileSpec spec)
+            throws ConfigurationException
+    {
+        save(out, spec.getEncoding());
+    }
+
+    /**
+     * Internal helper method for saving a file into the given writer.
+     *
+     * @param out the writer
+     * @throws ConfigurationException if an error occurs
+     */
+    private void saveToWriter(Writer out) throws ConfigurationException
+    {
+        try
+        {
+            getContent().write(out);
+        }
+        catch (IOException ioex)
+        {
+            throw new ConfigurationException(ioex);
+        }
+    }
+
+    /**
+     * Creates a snapshot of the current location data of the associated file.
+     * This snapshot can be used in code which does not have to synchronize on
+     * the internal {@code FileSpec} object.
+     *
+     * @return a snapshot of the current file location
+     */
+    private FileSpec snapshotFileSpec()
+    {
+        synchronized (fileSpec)
+        {
+            return fileSpec.clone();
+        }
+    }
+
+    /**
+     * Checks whether a content object is available. If not, an exception is
+     * thrown. This method is called whenever the content object is accessed.
+     *
+     * @throws ConfigurationException if not content object is defined
+     */
+    private void checkContent() throws ConfigurationException
+    {
+        if (getContent() == null)
+        {
+            throw new ConfigurationException("No content available!");
+        }
+    }
+
+    /**
+     * Checks whether a content object is available and creates a snapshot from
+     * the current file specification. 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 a snapshot of the current file location
+     */
+    private FileSpec checkContentAndCreateSnapshotFileSpec()
+            throws ConfigurationException
+    {
+        checkContent();
+        return snapshotFileSpec();
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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(Closeable cl)
+    {
+        try
+        {
+            if (cl != null)
+            {
+                cl.close();
+            }
+        }
+        catch (IOException e)
+        {
+            // ignore
+        }
+    }
+
+    /**
+     * A bean class defining the location of a file.
+     */
+    private static class FileSpec implements Cloneable
+    {
+        /** Stores the file name. */
+        private String fileName;
+
+        /** Stores the base path. */
+        private String basePath;
+
+        /** Stores the URL of the associated file. */
+        private URL sourceURL;
+
+        /** Stores the encoding for binary streams. */
+        private String encoding;
+
+        /** The FileSystem being used for this Configuration */
+        private FileSystem fileSystem = FileSystem.getDefaultFileSystem();
+
+        public String getFileName()
+        {
+            return fileName;
+        }
+
+        public void setFileName(String fileName)
+        {
+            this.fileName = fileName;
+        }
+
+        public String getBasePath()
+        {
+            return basePath;
+        }
+
+        public void setBasePath(String basePath)
+        {
+            this.basePath = basePath;
+        }
+
+        public URL getSourceURL()
+        {
+            return sourceURL;
+        }
+
+        public void setSourceURL(URL sourceURL)
+        {
+            this.sourceURL = sourceURL;
+        }
+
+        public String getEncoding()
+        {
+            return encoding;
+        }
+
+        public void setEncoding(String encoding)
+        {
+            this.encoding = encoding;
+        }
+
+        public FileSystem getFileSystem()
+        {
+            return fileSystem;
+        }
+
+        public void setFileSystem(FileSystem fileSystem)
+        {
+            this.fileSystem = fileSystem;
+        }
+
+        @Override
+        protected FileSpec clone()
+        {
+            try
+            {
+                return (FileSpec) super.clone();
+            }
+            catch (CloneNotSupportedException cex)
+            {
+                // should not happen
+                throw new AssertionError("Could not clone!");
+            }
+        }
+    }
+}

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/io/FileHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java?rev=1391574&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java Fri Sep 28 17:54:34 2012
@@ -0,0 +1,790 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.FileSystem;
+import org.easymock.EasyMock;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test class for {@code FileHandler}.
+ *
+ * @version $Id$
+ */
+public class TestFileHandler
+{
+    /** Constant for the name of a test file. */
+    private static final String TEST_FILENAME = "test.properties";
+
+    /** Constant for content of the test file. */
+    private static final String CONTENT =
+            "TestFileHandler: This is test content.";
+
+    /** Helper object for managing temporary files. */
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    /**
+     * Creates a test file test content and allows specifying a file name.
+     *
+     * @param f the file to be created (may be <b>null</b>)
+     * @return the File object pointing to the test file
+     */
+    private File createTestFile(File f)
+    {
+        Writer out = null;
+        File file = f;
+        try
+        {
+            if (file == null)
+            {
+                file = folder.newFile();
+            }
+            out = new FileWriter(file);
+            out.write(CONTENT);
+        }
+        catch (IOException ioex)
+        {
+            fail("Could not create test file: " + ioex);
+            return null; // cannot happen
+        }
+        finally
+        {
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException ioex)
+                {
+                    // ignore
+                }
+            }
+        }
+        return file;
+    }
+
+    /**
+     * Creates a test file with the test content.
+     *
+     * @return the File object pointing to the test file
+     */
+    private File createTestFile()
+    {
+        return createTestFile(null);
+    }
+
+    /**
+     * Reads the content of the specified reader into a string.
+     *
+     * @param in the reader
+     * @return the read content
+     * @throws IOException if an error occurs
+     */
+    private static String readReader(Reader in) throws IOException
+    {
+        StringBuilder buf = new StringBuilder();
+        int c;
+        while ((c = in.read()) != -1)
+        {
+            buf.append((char) c);
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Reads the content of the specified file into a string
+     *
+     * @param f the file to be read
+     * @return the content of this file
+     */
+    private static String readFile(File f)
+    {
+        Reader in = null;
+        try
+        {
+            in = new FileReader(f);
+            return readReader(in);
+        }
+        catch (IOException ioex)
+        {
+            fail("Could not read file: " + ioex);
+            return null; // cannot happen
+        }
+        finally
+        {
+            if (in != null)
+            {
+                try
+                {
+                    in.close();
+                }
+                catch (IOException ioex)
+                {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests whether a newly created instance has a default file system.
+     */
+    @Test
+    public void testGetFileSystemDefault()
+    {
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        assertEquals("Wrong default file system",
+                FileSystem.getDefaultFileSystem(), handler.getFileSystem());
+    }
+
+    /**
+     * Tests whether a null file system can be set to reset this property.
+     */
+    @Test
+    public void testSetFileSystemNull()
+    {
+        FileSystem sys = EasyMock.createMock(FileSystem.class);
+        EasyMock.replay(sys);
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setFileSystem(sys);
+        assertSame("File system not set", sys, handler.getFileSystem());
+        handler.setFileSystem(null);
+        assertEquals("Not default file system",
+                FileSystem.getDefaultFileSystem(), handler.getFileSystem());
+    }
+
+    /**
+     * Tests whether the file system can be reset.
+     */
+    @Test
+    public void testResetFileSystem()
+    {
+        FileSystem sys = EasyMock.createMock(FileSystem.class);
+        EasyMock.replay(sys);
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setFileSystem(sys);
+        handler.resetFileSystem();
+        assertEquals("Not default file system",
+                FileSystem.getDefaultFileSystem(), handler.getFileSystem());
+    }
+
+    /**
+     * Tests whether a URL can be set.
+     */
+    @Test
+    public void testSetURL() throws Exception
+    {
+        FileHandler handler = new FileHandler();
+        handler.setURL(new URL(
+                "http://commons.apache.org/configuration/index.html"));
+
+        assertEquals("base path", "http://commons.apache.org/configuration/",
+                handler.getBasePath());
+        assertEquals("file name", "index.html", handler.getFileName());
+
+        // file URL - This url is invalid, a valid url would be
+        // file:///temp/test.properties.
+        handler.setURL(new URL("file:/temp/test.properties"));
+        assertEquals("base path", "file:///temp/", handler.getBasePath());
+        assertEquals("file name", TEST_FILENAME, handler.getFileName());
+    }
+
+    /**
+     * Tests whether a URL with parameters can be set.
+     */
+    @Test
+    public void testSetURLWithParams() throws Exception
+    {
+        FileHandler handler = new FileHandler();
+        URL url =
+                new URL(
+                        "http://issues.apache.org/bugzilla/show_bug.cgi?id=37886");
+        handler.setURL(url);
+        assertEquals("Base path incorrect",
+                "http://issues.apache.org/bugzilla/", handler.getBasePath());
+        assertEquals("File name incorrect", "show_bug.cgi",
+                handler.getFileName());
+        assertEquals("URL was not correctly stored", url, handler.getURL());
+    }
+
+    /**
+     * Tests whether the location can be set as a file.
+     */
+    @Test
+    public void testSetFile()
+    {
+        FileHandler handler = new FileHandler();
+        File directory = ConfigurationAssert.TEST_DIR;
+        File file = ConfigurationAssert.getTestFile(TEST_FILENAME);
+        handler.setFile(file);
+        assertEquals("Wrong base path", directory.getAbsolutePath(),
+                handler.getBasePath());
+        assertEquals("Wrong file name", TEST_FILENAME, handler.getFileName());
+        assertEquals("Wrong path", file.getAbsolutePath(), handler.getPath());
+    }
+
+    /**
+     * Tests whether the location can be set as a file.
+     */
+    @Test
+    public void testSetPath() throws MalformedURLException
+    {
+        FileHandler config = new FileHandler();
+        config.setPath(ConfigurationAssert.TEST_DIR_NAME + File.separator
+                + TEST_FILENAME);
+        assertEquals("Wrong file name", TEST_FILENAME, config.getFileName());
+        assertEquals("Wrong base path",
+                ConfigurationAssert.TEST_DIR.getAbsolutePath(),
+                config.getBasePath());
+        File file = ConfigurationAssert.getTestFile(TEST_FILENAME);
+        assertEquals("Wrong path", file.getAbsolutePath(), config.getPath());
+        assertEquals("Wrong URL", file.toURI().toURL(), config.getURL());
+    }
+
+    /**
+     * Tests whether the location can be set using file name and base path.
+     */
+    @Test
+    public void testSetFileName()
+    {
+        FileHandler config = new FileHandler();
+        config.setBasePath(null);
+        config.setFileName(TEST_FILENAME);
+        assertNull("Got a base path", config.getBasePath());
+        assertEquals("Wrong file name", TEST_FILENAME, config.getFileName());
+    }
+
+    /**
+     * Tries to call a load() method if no content object is available.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadNoContent() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler();
+        StringReader reader = new StringReader(CONTENT);
+        handler.load(reader);
+    }
+
+    /**
+     * Tests whether an IOException is handled when loading data from a reader.
+     */
+    @Test
+    public void testLoadFromReaderIOException() throws IOException,
+            ConfigurationException
+    {
+        FileBased content = EasyMock.createMock(FileBased.class);
+        Reader in = new StringReader(CONTENT);
+        IOException ioex = new IOException("Test exception");
+        content.read(in);
+        EasyMock.expectLastCall().andThrow(ioex);
+        EasyMock.replay(content);
+        FileHandler handler = new FileHandler(content);
+        try
+        {
+            handler.load(in);
+            fail("IOException not detected!");
+        }
+        catch (ConfigurationException cex)
+        {
+            assertEquals("Wrong root cause", ioex, cex.getCause());
+        }
+        EasyMock.verify(content);
+    }
+
+    /**
+     * Tests whether data from a File can be loaded.
+     */
+    @Test
+    public void testLoadFromFile() throws ConfigurationException
+    {
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        File file = createTestFile();
+        FileHandler handler = new FileHandler(content);
+        handler.load(file);
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tries to load data from a File if no content object was set.
+     */
+    @Test
+    public void testLoadFromFileNoContent() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler();
+        File file = createTestFile();
+        try
+        {
+            handler.load(file);
+            fail("Missing content not detected!");
+        }
+        catch (ConfigurationException cex)
+        {
+            assertEquals("Wrong message", "No content available!",
+                    cex.getMessage());
+        }
+    }
+
+    /**
+     * Checks that loading a directory instead of a file throws an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryString() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.load(ConfigurationAssert.TEST_DIR.getAbsolutePath());
+    }
+
+    /**
+     * Tests that it is not possible to load a directory using the load() method
+     * which expects a File.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryFile() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.load(ConfigurationAssert.TEST_DIR);
+    }
+
+    /**
+     * Tests whether whether data can be loaded from class path.
+     */
+    @Test
+    public void testLoadFromClassPath() throws ConfigurationException
+    {
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler config1 = new FileHandler(content);
+        config1.setFileName("config/deep/deeptest.properties");
+        config1.load();
+        assertTrue("No data loaded", content.getContent().length() > 0);
+    }
+
+    /**
+     * Tests whether data from a URL can be loaded.
+     */
+    @Test
+    public void testLoadFromURL() throws Exception
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.load(file.toURI().toURL());
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tests whether data from an absolute path can be loaded.
+     */
+    @Test
+    public void testLoadFromFilePath() throws ConfigurationException
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.load(file.getAbsolutePath());
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tests whether data from an input stream can be read.
+     */
+    @Test
+    public void testLoadFromStream() throws Exception
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        FileInputStream in = new FileInputStream(file);
+        try
+        {
+            handler.load(in);
+        }
+        finally
+        {
+            in.close();
+        }
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tests whether data from a reader can be read.
+     */
+    @Test
+    public void testLoadFromReader() throws Exception
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        Reader in = new FileReader(file);
+        try
+        {
+            handler.load(in);
+        }
+        finally
+        {
+            in.close();
+        }
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tests a load operation using the current location which is a URL.
+     */
+    @Test
+    public void testLoadFromURLLocation() throws Exception
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.setURL(file.toURI().toURL());
+        handler.load();
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tests a load operation using the current location which is a file name.
+     */
+    @Test
+    public void testLoadFromFileNameLocation() throws ConfigurationException
+    {
+        File file = createTestFile();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.setBasePath(file.getParentFile().getAbsolutePath());
+        handler.setFileName(file.getName());
+        handler.load();
+        assertEquals("Wrong content", CONTENT, content.getContent());
+    }
+
+    /**
+     * Tries to load data if no location has been set.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadNoLocation() throws ConfigurationException
+    {
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.load();
+    }
+
+    /**
+     * Tests whether data can be saved into a Writer.
+     */
+    @Test
+    public void testSaveToWriter() throws ConfigurationException
+    {
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        StringWriter out = new StringWriter();
+        handler.save(out);
+        assertEquals("Wrong content", CONTENT, out.toString());
+    }
+
+    /**
+     * Tests whether an I/O exception during a save operation to a Writer is
+     * handled correctly.
+     */
+    @Test
+    public void testSaveToWriterIOException() throws ConfigurationException,
+            IOException
+    {
+        FileBased content = EasyMock.createMock(FileBased.class);
+        StringWriter out = new StringWriter();
+        IOException ioex = new IOException("Test exception!");
+        content.write(out);
+        EasyMock.expectLastCall().andThrow(ioex);
+        EasyMock.replay(content);
+        FileHandler handler = new FileHandler(content);
+        try
+        {
+            handler.save(out);
+            fail("IOException not detected!");
+        }
+        catch (ConfigurationException cex)
+        {
+            assertEquals("Wrong cause", ioex, cex.getCause());
+        }
+        EasyMock.verify(content);
+    }
+
+    /**
+     * Tries to save something to a Writer if no content is set.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testSaveToWriterNoContent() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler();
+        handler.save(new StringWriter());
+    }
+
+    /**
+     * Tests whether data can be saved to a stream.
+     */
+    @Test
+    public void testSaveToStream() throws ConfigurationException, IOException
+    {
+        File file = folder.newFile();
+        FileOutputStream out = new FileOutputStream(file);
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        try
+        {
+            handler.save(out);
+        }
+        finally
+        {
+            out.close();
+        }
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tests whether data can be saved to a file.
+     */
+    @Test
+    public void testSaveToFile() throws ConfigurationException, IOException
+    {
+        File file = folder.newFile();
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.save(file);
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tests whether data can be saved to a URL.
+     */
+    @Test
+    public void testSaveToURL() throws Exception
+    {
+        File file = folder.newFile();
+        URL url = file.toURI().toURL();
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.save(url);
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tests whether data can be saved to a file name.
+     */
+    @Test
+    public void testSaveToFileName() throws ConfigurationException, IOException
+    {
+        File file = folder.newFile();
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.save(file.getAbsolutePath());
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tests whether a URL exception is handled when saving a file to a file
+     * name.
+     */
+    @Test
+    public void testSaveToFileNameURLException() throws ConfigurationException,
+            IOException
+    {
+        FileSystem fs = EasyMock.createMock(FileSystem.class);
+        File file = folder.newFile();
+        String basePath = "some base path";
+        MalformedURLException urlex =
+                new MalformedURLException("Test exception");
+        EasyMock.expect(fs.getURL(basePath, file.getName())).andThrow(urlex);
+        EasyMock.replay(fs);
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setBasePath(basePath);
+        handler.setFileSystem(fs);
+        try
+        {
+            handler.save(file.getName());
+            fail("URL exception not detected!");
+        }
+        catch (ConfigurationException cex)
+        {
+            assertEquals("Wrong cause", urlex, cex.getCause());
+        }
+        EasyMock.verify(fs);
+    }
+
+    /**
+     * Tries to save data to a file name if the name cannot be located.
+     */
+    @Test
+    public void testSaveToFileNameURLNotResolved()
+            throws ConfigurationException, IOException
+    {
+        FileSystem fs = EasyMock.createMock(FileSystem.class);
+        File file = folder.newFile();
+        EasyMock.expect(fs.getURL(null, file.getName())).andReturn(null);
+        EasyMock.replay(fs);
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setFileSystem(fs);
+        try
+        {
+            handler.save(file.getName());
+            fail("Unresolved URL not detected!");
+        }
+        catch (ConfigurationException cex)
+        {
+            EasyMock.verify(fs);
+        }
+    }
+
+    /**
+     * Tests whether data can be saved to the internal location if it is a file
+     * name.
+     */
+    @Test
+    public void testSaveToFileNameLocation() throws ConfigurationException,
+            IOException
+    {
+        File file = folder.newFile();
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setFileName(file.getAbsolutePath());
+        handler.save();
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tests whether data can be saved to the internal location if it is a URL.
+     */
+    @Test
+    public void testSaveToURLLocation() throws ConfigurationException,
+            IOException
+    {
+        File file = folder.newFile();
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setURL(file.toURI().toURL());
+        handler.save();
+        assertEquals("Wrong content", CONTENT, readFile(file));
+    }
+
+    /**
+     * Tries to save the handler if no location has been set.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testSaveNoLocation() throws ConfigurationException
+    {
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.save();
+    }
+
+    /**
+     * Tests loading and saving a configuration file with a complicated path
+     * name including spaces. (related to issue 35210)
+     */
+    @Test
+    public void testPathWithSpaces() throws ConfigurationException, IOException
+    {
+        File path = folder.newFolder("path with spaces");
+        File confFile = new File(path, "config-test.properties");
+        File testFile = createTestFile(confFile);
+        URL url = testFile.toURI().toURL();
+        FileBasedTestImpl content = new FileBasedTestImpl();
+        FileHandler handler = new FileHandler(content);
+        handler.setURL(url);
+        handler.load();
+        assertEquals("Wrong data read", CONTENT, content.getContent());
+        File out = new File(path, "out.txt");
+        handler.save(out);
+        assertEquals("Wrong data written", CONTENT, readFile(out));
+    }
+
+    /**
+     * Tests whether file names containing a "+" character are handled
+     * correctly. This test is related to CONFIGURATION-415.
+     */
+    @Test
+    public void testPathWithPlus() throws ConfigurationException, IOException
+    {
+        File saveFile = folder.newFile("test+config.properties");
+        FileHandler handler = new FileHandler(new FileBasedTestImpl());
+        handler.setFile(saveFile);
+        handler.save();
+        assertEquals("Wrong content", CONTENT, readFile(saveFile));
+    }
+
+    /**
+     * Tests whether a FileHandler object can be used to specify a location and
+     * later be assigned to a FileBased object.
+     */
+    @Test
+    public void testAssignWithFileBased()
+    {
+        FileHandler h1 = new FileHandler();
+        File f = new File("testfile.txt");
+        h1.setFile(f);
+        FileBased content = new FileBasedTestImpl();
+        FileHandler h2 = new FileHandler(content, h1);
+        h1.setFileName("someOtherFile.txt");
+        assertSame("Content not set", content, h2.getContent());
+        assertEquals("Wrong location", f, h2.getFile());
+    }
+
+    /**
+     * An implementation of the FileBased interface used for test purposes.
+     */
+    private static class FileBasedTestImpl implements FileBased
+    {
+        /** The content read from a reader. */
+        private String content;
+
+        /**
+         * Returns the content read from a reader.
+         *
+         * @return the read content
+         */
+        public String getContent()
+        {
+            return content;
+        }
+
+        public void read(Reader in) throws ConfigurationException, IOException
+        {
+            content = readReader(in);
+        }
+
+        public void write(Writer out) throws ConfigurationException,
+                IOException
+        {
+            out.write(CONTENT);
+            out.flush();
+        }
+    }
+}

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/io/TestFileHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain