| /* |
| * 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.configuration2.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 java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.io.FileLocator.FileLocatorBuilder; |
| import org.apache.commons.configuration2.sync.LockMode; |
| import org.apache.commons.configuration2.sync.NoOpSynchronizer; |
| import org.apache.commons.configuration2.sync.Synchronizer; |
| import org.apache.commons.configuration2.sync.SynchronizerSupport; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * <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: |
| * </p> |
| * <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: 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> |
| * 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> |
| * The actual position of the file to be loaded is determined by a |
| * {@link FileLocationStrategy} based on the location information that has been |
| * provided. By providing a custom location strategy the algorithm for searching |
| * files can be adapted. Save operations require more explicit information. They |
| * cannot rely on a location strategy because the file to be written may not yet |
| * exist. So there may be some differences in the way location information is |
| * interpreted by load and save operations. In order to avoid this, the |
| * following approach is recommended: |
| * </p> |
| * <ul> |
| * <li>Use the desired {@code setXXX()} methods to define the location of the |
| * file to be loaded.</li> |
| * <li>Call the {@code locate()} method. This method resolves the referenced |
| * file (if possible) and fills out all supported location information.</li> |
| * <li>Later on, {@code save()} can be called. This method now has sufficient |
| * information to store the file at the correct location.</li> |
| * </ul> |
| * <p> |
| * When loading or saving a {@code FileBased} object some additional |
| * functionality is performed if the object implements one of the following |
| * interfaces: |
| * </p> |
| * <ul> |
| * <li>{@code FileLocatorAware}: In this case an object with the current file |
| * location is injected before the load or save operation is executed. This is |
| * useful for {@code FileBased} objects that depend on their current location, |
| * e.g. to resolve relative path names.</li> |
| * <li>{@code SynchronizerSupport}: If this interface is implemented, load and |
| * save operations obtain a write lock on the {@code FileBased} object before |
| * they access it. (In case of a save operation, a read lock would probably be |
| * sufficient, but because of the possible injection of a {@link FileLocator} |
| * object it is not allowed to perform multiple save operations in parallel; |
| * therefore, by obtaining a write lock, we are on the safe side.)</li> |
| * </ul> |
| * <p> |
| * This class is thread-safe. |
| * </p> |
| * |
| * @since 2.0 |
| */ |
| 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:"; |
| |
| /** Constant for the URI scheme for files with slashes. */ |
| private static final String FILE_SCHEME_SLASH = FILE_SCHEME + "//"; |
| |
| /** |
| * A dummy implementation of {@code SynchronizerSupport}. This object is |
| * used when the file handler's content does not implement the |
| * {@code SynchronizerSupport} interface. All methods are just empty dummy |
| * implementations. |
| */ |
| private static final SynchronizerSupport DUMMY_SYNC_SUPPORT = |
| new SynchronizerSupport() |
| { |
| @Override |
| public Synchronizer getSynchronizer() |
| { |
| return NoOpSynchronizer.INSTANCE; |
| } |
| |
| @Override |
| public void lock(final LockMode mode) |
| { |
| } |
| |
| @Override |
| public void setSynchronizer(final Synchronizer sync) |
| { |
| } |
| |
| @Override |
| public void unlock(final LockMode mode) |
| { |
| } |
| }; |
| |
| /** |
| * 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; |
| |
| /** A reference to the current {@code FileLocator} object. */ |
| private final AtomicReference<FileLocator> fileLocator; |
| |
| /** A collection with the registered listeners. */ |
| private final List<FileHandlerListener> listeners = |
| new CopyOnWriteArrayList<>(); |
| |
| /** |
| * 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(final FileBased obj) |
| { |
| this(obj, emptyFileLocator()); |
| } |
| |
| /** |
| * 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(final FileBased obj, final FileHandler c) |
| { |
| this(obj, checkSourceHandler(c).getFileLocator()); |
| } |
| |
| /** |
| * Creates a new instance of {@code FileHandler} based on the given |
| * {@code FileBased} and {@code FileLocator} objects. |
| * |
| * @param obj the {@code FileBased} object to manage |
| * @param locator the {@code FileLocator} |
| */ |
| private FileHandler(final FileBased obj, final FileLocator locator) |
| { |
| content = obj; |
| fileLocator = new AtomicReference<>(locator); |
| } |
| |
| /** |
| * Adds a listener to this {@code FileHandler}. It is notified about |
| * property changes and IO operations. |
| * |
| * @param l the listener to be added (must not be <b>null</b>) |
| * @throws IllegalArgumentException if the listener is <b>null</b> |
| */ |
| public void addFileHandlerListener(final FileHandlerListener l) |
| { |
| if (l == null) |
| { |
| throw new IllegalArgumentException("Listener must not be null!"); |
| } |
| listeners.add(l); |
| } |
| |
| /** |
| * 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 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 current {@code FileLocator} to be used for the calling |
| * operation |
| */ |
| private FileLocator checkContentAndGetLocator() |
| throws ConfigurationException |
| { |
| checkContent(); |
| return getFileLocator(); |
| } |
| |
| /** |
| * Clears the location of this {@code FileHandler}. Afterwards this handler |
| * does not point to any valid file. |
| */ |
| public void clearLocation() |
| { |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.basePath(null).fileName(null).sourceURL(null); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Notifies the registered listeners about a completed load operation. |
| */ |
| private void fireLoadedEvent() |
| { |
| for (final FileHandlerListener l : listeners) |
| { |
| l.loaded(this); |
| } |
| } |
| |
| /** |
| * 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 property update. |
| */ |
| private void fireLocationChangedEvent() |
| { |
| for (final FileHandlerListener l : listeners) |
| { |
| l.locationChanged(this); |
| } |
| } |
| |
| /** |
| * Notifies the registered listeners about a completed save operation. |
| */ |
| private void fireSavedEvent() |
| { |
| for (final FileHandlerListener l : listeners) |
| { |
| l.saved(this); |
| } |
| } |
| |
| /** |
| * 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 base path |
| */ |
| public String getBasePath() |
| { |
| final FileLocator locator = getFileLocator(); |
| if (locator.getBasePath() != null) |
| { |
| return locator.getBasePath(); |
| } |
| |
| if (locator.getSourceURL() != null) |
| { |
| return FileLocatorUtils.getBasePath(locator.getSourceURL()); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the {@code FileBased} object associated with this |
| * {@code FileHandler}. |
| * |
| * @return the associated {@code FileBased} object |
| */ |
| public final FileBased getContent() |
| { |
| return content; |
| } |
| |
| /** |
| * 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() |
| { |
| return getFileLocator().getEncoding(); |
| } |
| |
| /** |
| * 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> |
| */ |
| public File getFile() |
| { |
| return createFile(getFileLocator()); |
| } |
| |
| /** |
| * Returns a {@code FileLocator} object with the specification of the file |
| * stored by this {@code FileHandler}. Note that this method returns the |
| * internal data managed by this {@code FileHandler} as it was defined. |
| * This is not necessarily the same as the data returned by the single |
| * access methods like {@code getFileName()} or {@code getURL()}: These |
| * methods try to derive missing data from other values that have been set. |
| * |
| * @return a {@code FileLocator} with the referenced file |
| */ |
| public FileLocator getFileLocator() |
| { |
| return fileLocator.get(); |
| } |
| |
| /** |
| * Return the name of the file. If only a URL is defined, the file name |
| * is derived from there. |
| * |
| * @return the file name |
| */ |
| public String getFileName() |
| { |
| final FileLocator locator = getFileLocator(); |
| if (locator.getFileName() != null) |
| { |
| return locator.getFileName(); |
| } |
| |
| if (locator.getSourceURL() != null) |
| { |
| return FileLocatorUtils.getFileName(locator.getSourceURL()); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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() |
| { |
| return FileLocatorUtils.obtainFileSystem(getFileLocator()); |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * 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() |
| { |
| final FileLocator locator = getFileLocator(); |
| final File file = createFile(locator); |
| return FileLocatorUtils.obtainFileSystem(locator).getPath(file, |
| locator.getSourceURL(), locator.getBasePath(), locator.getFileName()); |
| } |
| |
| /** |
| * 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. |
| * |
| * @return a URL to the associated file; can be <b>null</b> if the location |
| * is unspecified |
| */ |
| public URL getURL() |
| { |
| final FileLocator locator = getFileLocator(); |
| return locator.getSourceURL() != null ? locator.getSourceURL() |
| : FileLocatorUtils.locate(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); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| |
| /** |
| * Tests whether a location is defined for this {@code FileHandler}. |
| * |
| * @return <b>true</b> if a location is defined, <b>false</b> otherwise |
| */ |
| public boolean isLocationDefined() |
| { |
| return FileLocatorUtils.isLocationDefined(getFileLocator()); |
| } |
| |
| /** |
| * 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(checkContentAndGetLocator()); |
| } |
| |
| /** |
| * Loads the associated file from the specified {@code File}. |
| * |
| * @param file the file to load |
| * @throws ConfigurationException if an error occurs |
| */ |
| public void load(final File file) throws ConfigurationException |
| { |
| URL url; |
| try |
| { |
| url = FileLocatorUtils.toURL(file); |
| } |
| catch (final MalformedURLException e1) |
| { |
| throw new ConfigurationException("Cannot create URL from file " |
| + file); |
| } |
| |
| load(url); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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(final InputStream in) throws ConfigurationException |
| { |
| load(in, checkContentAndGetLocator()); |
| } |
| |
| /** |
| * 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. |
| * |
| * @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(final InputStream in, final String encoding) |
| throws ConfigurationException |
| { |
| loadFromStream(in, encoding, null); |
| } |
| |
| /** |
| * 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(final Reader in) throws ConfigurationException |
| { |
| checkContent(); |
| injectNullFileLocator(); |
| loadFromReader(in); |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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(final URL url) throws ConfigurationException |
| { |
| load(url, checkContentAndGetLocator()); |
| } |
| |
| /** |
| * Internal helper method for loading a file from the given URL. |
| * |
| * @param url the URL |
| * @param locator the current {@code FileLocator} |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void load(final URL url, final FileLocator locator) throws ConfigurationException |
| { |
| 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); |
| } |
| } |
| |
| /** |
| * Internal helper method for loading a file from the given reader. |
| * |
| * @param in the reader |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void loadFromReader(final Reader in) throws ConfigurationException |
| { |
| fireLoadingEvent(); |
| try |
| { |
| getContent().read(in); |
| } |
| catch (final IOException ioex) |
| { |
| throw new ConfigurationException(ioex); |
| } |
| finally |
| { |
| fireLoadedEvent(); |
| } |
| } |
| |
| /** |
| * Internal helper method for loading a file from an input stream. |
| * |
| * @param in the input stream |
| * @param encoding the encoding |
| * @param url the URL of the file to be loaded (if known) |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void loadFromStream(final InputStream in, final String encoding, final URL url) |
| throws ConfigurationException |
| { |
| checkContent(); |
| final SynchronizerSupport syncSupport = fetchSynchronizerSupport(); |
| syncSupport.lock(LockMode.WRITE); |
| try |
| { |
| injectFileLocator(url); |
| |
| if (getContent() instanceof InputStreamSupport) |
| { |
| loadFromStreamDirectly(in); |
| } |
| else |
| { |
| loadFromTransformedStream(in, encoding); |
| } |
| } |
| finally |
| { |
| syncSupport.unlock(LockMode.WRITE); |
| } |
| } |
| |
| /** |
| * Loads data from an input stream if the associated {@code FileBased} |
| * object implements the {@code InputStreamSupport} interface. |
| * |
| * @param in the input stream |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void loadFromStreamDirectly(final InputStream in) |
| throws ConfigurationException |
| { |
| try |
| { |
| ((InputStreamSupport) getContent()).read(in); |
| } |
| catch (final IOException e) |
| { |
| throw new ConfigurationException(e); |
| } |
| } |
| |
| /** |
| * Internal helper method for transforming an input stream to a reader and |
| * reading its content. |
| * |
| * @param in the input stream |
| * @param encoding the encoding |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void loadFromTransformedStream(final InputStream in, final String encoding) |
| throws ConfigurationException |
| { |
| Reader reader = null; |
| |
| if (encoding != null) |
| { |
| try |
| { |
| reader = new InputStreamReader(in, encoding); |
| } |
| catch (final 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); |
| } |
| |
| /** |
| * 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. |
| * |
| * @return a flag whether the referenced file could be located successfully |
| * @see FileLocatorUtils#fullyInitializedLocator(FileLocator) |
| */ |
| public boolean locate() |
| { |
| 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; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * Internal helper method for saving data to the given file name. |
| * |
| * @param fileName the path to the target file |
| * @param locator the current {@code FileLocator} |
| * @throws ConfigurationException if an error occurs during the save |
| * operation |
| */ |
| private void save(final String fileName, final FileLocator locator) |
| throws ConfigurationException |
| { |
| URL url; |
| try |
| { |
| url = FileLocatorUtils.obtainFileSystem(locator).getURL( |
| locator.getBasePath(), fileName); |
| } |
| catch (final MalformedURLException e) |
| { |
| throw new ConfigurationException(e); |
| } |
| |
| if (url == null) |
| { |
| throw new ConfigurationException( |
| "Cannot locate configuration source " + fileName); |
| } |
| save(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). |
| * |
| * @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 |
| * @param locator the {@code FileLocator} |
| * @throws ConfigurationException if an error occurs during the save |
| * operation |
| */ |
| private void save(final URL url, final FileLocator locator) throws ConfigurationException |
| { |
| OutputStream out = null; |
| try |
| { |
| out = FileLocatorUtils.obtainFileSystem(locator).getOutputStream(url); |
| saveToStream(out, locator.getEncoding(), url); |
| if (out instanceof VerifiableOutputStream) |
| { |
| try |
| { |
| ((VerifiableOutputStream) out).verify(); |
| } |
| catch (final IOException e) |
| { |
| throw new ConfigurationException(e); |
| } |
| } |
| } |
| finally |
| { |
| closeSilent(out); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * Internal helper method for saving a file to the given stream. |
| * |
| * @param out the output stream |
| * @param encoding the encoding |
| * @param url the URL of the output file if known |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void saveToStream(final OutputStream out, final String encoding, final URL url) |
| throws ConfigurationException |
| { |
| checkContent(); |
| final SynchronizerSupport syncSupport = fetchSynchronizerSupport(); |
| syncSupport.lock(LockMode.WRITE); |
| try |
| { |
| injectFileLocator(url); |
| Writer writer = null; |
| |
| if (encoding != null) |
| { |
| try |
| { |
| writer = new OutputStreamWriter(out, encoding); |
| } |
| catch (final 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); |
| } |
| finally |
| { |
| syncSupport.unlock(LockMode.WRITE); |
| } |
| } |
| |
| /** |
| * Internal helper method for saving a file into the given writer. |
| * |
| * @param out the writer |
| * @throws ConfigurationException if an error occurs |
| */ |
| private void saveToWriter(final Writer out) throws ConfigurationException |
| { |
| fireSavingEvent(); |
| try |
| { |
| getContent().write(out); |
| } |
| catch (final IOException ioex) |
| { |
| throw new ConfigurationException(ioex); |
| } |
| finally |
| { |
| fireSavedEvent(); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setBasePath(final String basePath) |
| { |
| final String path = normalizeFileURL(basePath); |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.basePath(path); |
| builder.sourceURL(null); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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. |
| * |
| * @param encoding the encoding of the associated file |
| */ |
| public void setEncoding(final String encoding) |
| { |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.encoding(encoding); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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(final File file) |
| { |
| final String fileName = file.getName(); |
| final String basePath = |
| file.getParentFile() != null ? file.getParentFile() |
| .getAbsolutePath() : null; |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.fileName(fileName).basePath(basePath).sourceURL(null); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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> |
| */ |
| public void setFileLocator(final FileLocator locator) |
| { |
| if (locator == null) |
| { |
| throw new IllegalArgumentException("FileLocator must not be null!"); |
| } |
| |
| fileLocator.set(locator); |
| fireLocationChangedEvent(); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void setFileName(final String fileName) |
| { |
| final String name = normalizeFileURL(fileName); |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.fileName(name); |
| builder.sourceURL(null); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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(final FileSystem fileSystem) |
| { |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.fileSystem(fileSystem); |
| } |
| } |
| .update(); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void setLocationStrategy(final FileLocationStrategy strategy) |
| { |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.locationStrategy(strategy); |
| } |
| |
| } |
| .update(); |
| } |
| |
| /** |
| * 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(final String path) |
| { |
| setFile(new File(path)); |
| } |
| |
| /** |
| * 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 url the location of the file as URL |
| */ |
| public void setURL(final URL url) |
| { |
| new Updater() |
| { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) |
| { |
| builder.sourceURL(url); |
| builder.basePath(null).fileName(null); |
| } |
| } |
| .update(); |
| } |
| } |