| /* |
| * 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 AbstractUpdater { |
| /** |
| * 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) { |
| // empty |
| } |
| |
| @Override |
| public void setSynchronizer(final Synchronizer sync) { |
| // empty |
| } |
| |
| @Override |
| public void unlock(final LockMode mode) { |
| // empty |
| } |
| }; |
| |
| /** 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); |
| } |
| |
| /** |
| * 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; |
| } |
| if (loc.getSourceURL() != null) { |
| return FileLocatorUtils.fileFromURL(loc.getSourceURL()); |
| } |
| 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; |
| } |
| |
| /** |
| * 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 |
| * @throws ConfigurationException if not content object is defined |
| */ |
| 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 AbstractUpdater() { |
| @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() { |
| listeners.forEach(l -> l.loaded(this)); |
| } |
| |
| /** |
| * Notifies the registered listeners about the start of a load operation. |
| */ |
| private void fireLoadingEvent() { |
| listeners.forEach(l -> l.loading(this)); |
| } |
| |
| /** |
| * Notifies the registered listeners about a property update. |
| */ |
| private void fireLocationChangedEvent() { |
| listeners.forEach(l -> l.locationChanged(this)); |
| } |
| |
| /** |
| * Notifies the registered listeners about a completed save operation. |
| */ |
| private void fireSavedEvent() { |
| listeners.forEach(l -> l.saved(this)); |
| } |
| |
| /** |
| * Notifies the registered listeners about the start of a save operation. |
| */ |
| private void fireSavingEvent() { |
| listeners.forEach(l -> l.saving(this)); |
| } |
| |
| /** |
| * Gets 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; |
| } |
| |
| /** |
| * Gets the {@code FileBased} object associated with this {@code FileHandler}. |
| * |
| * @return the associated {@code FileBased} object |
| */ |
| public final FileBased getContent() { |
| return content; |
| } |
| |
| /** |
| * Gets 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(); |
| } |
| |
| /** |
| * Gets 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()); |
| } |
| |
| /** |
| * Gets 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(); |
| } |
| |
| /** |
| * Gets 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; |
| } |
| |
| /** |
| * Gets 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.getFileSystem(getFileLocator()); |
| } |
| |
| /** |
| * Gets 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.getLocationStrategy(getFileLocator()); |
| } |
| |
| /** |
| * Gets 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.getFileSystem(locator).getPath(file, locator.getSourceURL(), locator.getBasePath(), locator.getFileName()); |
| } |
| |
| /** |
| * Gets 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 { |
| final 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 { |
| load(FileLocatorUtils.locateOrThrow(locator), 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 { |
| final FileSystem fileSystem = FileLocatorUtils.getFileSystem(locator); |
| final URLConnectionOptions urlConnectionOptions = locator.getURLConnectionOptions(); |
| in = urlConnectionOptions == null ? fileSystem.getInputStream(url) : fileSystem.getInputStream(url, urlConnectionOptions); |
| 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.getFileSystem(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 { |
| final URL url; |
| try { |
| url = FileLocatorUtils.getFileSystem(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.getFileSystem(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 AbstractUpdater() { |
| @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 AbstractUpdater() { |
| @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 AbstractUpdater() { |
| @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(); |
| } |
| |
| /** |
| * Sets 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 AbstractUpdater() { |
| @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 AbstractUpdater() { |
| @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 AbstractUpdater() { |
| @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) { |
| setURL(url, URLConnectionOptions.DEFAULT); |
| } |
| |
| /** |
| * 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 |
| * @param urlConnectionOptions URL connection options |
| * @since 2.8.0 |
| */ |
| public void setURL(final URL url, final URLConnectionOptions urlConnectionOptions) { |
| new AbstractUpdater() { |
| @Override |
| protected void updateBuilder(final FileLocatorBuilder builder) { |
| builder.sourceURL(url); |
| builder.urlConnectionOptions(urlConnectionOptions); |
| builder.basePath(null).fileName(null); |
| } |
| }.update(); |
| } |
| } |