FileHandler.java
- /*
- * 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, for example
- * 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
- }
- };
- /**
- * Helper method for checking a file handler which is to be copied. Throws an exception if the handler is <strong>null</strong>.
- *
- * @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 <strong>null</strong>)
- */
- 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 <strong>null</strong>.
- *
- * @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 <strong>null</strong>)
- * @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 <strong>null</strong>)
- * @throws IllegalArgumentException if the {@code FileHandler} is <strong>null</strong>
- */
- 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 <strong>null</strong>)
- * @throws IllegalArgumentException if the listener is <strong>null</strong>
- */
- 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 <strong>null</strong> 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 <strong>null</strong>
- */
- 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 <strong>null</strong>; 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
- * <strong>null</strong>. 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 <strong>null</strong> 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 <strong>null</strong> values. This method is called if
- * no file location is available (for example 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 <strong>true</strong> if a location is defined, <strong>false</strong> 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 <strong>null</strong>,
- * 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 (for example 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 (for example 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 <strong>false</strong>. 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 (for example
- * 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 <strong>null</strong>, 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
- * (for example {@code setFile()} or {@code setURL()}), the base path is automatically set. Setting the base path using this
- * method automatically sets the URL to <strong>null</strong> 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 <strong>null</strong>
- * @throws IllegalArgumentException if the {@code FileLocator} is <strong>null</strong>
- */
- 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
- * <strong>null</strong> 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 <strong>null</strong> 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 <strong>null</strong>; 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 <strong>null</strong>. 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 <strong>null</strong>. 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();
- }
- }