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 <garydgreg...@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 "file", - * 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 "file" 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 "file", + * 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 "file" 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(); } }