| /* |
| * 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.builder; |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.apache.commons.configuration2.FileBasedConfiguration; |
| import org.apache.commons.configuration2.PropertiesConfiguration; |
| import org.apache.commons.configuration2.XMLPropertiesConfiguration; |
| import org.apache.commons.configuration2.event.ConfigurationEvent; |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.io.FileHandler; |
| import org.apache.commons.lang3.ClassUtils; |
| import org.apache.commons.lang3.StringUtils; |
| |
| /** |
| * <p> |
| * A specialized {@code ConfigurationBuilder} implementation which can handle |
| * configurations read from a {@link FileHandler}. |
| * </p> |
| * <p> |
| * This class extends its base class by the support of a |
| * {@link FileBasedBuilderParametersImpl} object, and especially of the |
| * {@link FileHandler} contained in this object. When the builder creates a new |
| * object the resulting {@code Configuration} instance is associated with the |
| * {@code FileHandler}. If the {@code FileHandler} has a location set, the |
| * {@code Configuration} is directly loaded from this location. |
| * </p> |
| * <p> |
| * The {@code FileHandler} is kept by this builder and can be queried later on. |
| * It can be used for instance to save the current {@code Configuration} after |
| * it was modified. Some care has to be taken when changing the location of the |
| * {@code FileHandler}: The new location is recorded and also survives an |
| * invocation of the {@code resetResult()} method. However, when the builder's |
| * initialization parameters are reset by calling {@code resetParameters()} the |
| * location is reset, too. |
| * </p> |
| * |
| * @version $Id$ |
| * @since 2.0 |
| * @param <T> the concrete type of {@code Configuration} objects created by this |
| * builder |
| */ |
| public class FileBasedConfigurationBuilder<T extends FileBasedConfiguration> |
| extends BasicConfigurationBuilder<T> |
| { |
| /** A map for storing default encodings for specific configuration classes. */ |
| private static final Map<Class<?>, String> DEFAULT_ENCODINGS = |
| initializeDefaultEncodings(); |
| |
| /** Stores the FileHandler associated with the current configuration. */ |
| private FileHandler currentFileHandler; |
| |
| /** A specialized listener for the auto save mechanism. */ |
| private AutoSaveListener autoSaveListener; |
| |
| /** A flag whether the builder's parameters were reset. */ |
| private boolean resetParameters; |
| |
| /** |
| * Creates a new instance of {@code FileBasedConfigurationBuilder} which |
| * produces result objects of the specified class. |
| * |
| * @param resCls the result class (must not be <b>null</b> |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public FileBasedConfigurationBuilder(Class<? extends T> resCls) |
| { |
| super(resCls); |
| } |
| |
| /** |
| * Creates a new instance of {@code FileBasedConfigurationBuilder} which |
| * produces result objects of the specified class and sets initialization |
| * parameters. |
| * |
| * @param resCls the result class (must not be <b>null</b> |
| * @param params a map with initialization parameters |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public FileBasedConfigurationBuilder(Class<? extends T> resCls, |
| Map<String, Object> params) |
| { |
| super(resCls, params); |
| } |
| |
| /** |
| * Creates a new instance of {@code FileBasedConfigurationBuilder} which |
| * produces result objects of the specified class and sets initialization |
| * parameters and the <em>allowFailOnInit</em> flag. |
| * |
| * @param resCls the result class (must not be <b>null</b> |
| * @param params a map with initialization parameters |
| * @param allowFailOnInit the <em>allowFailOnInit</em> flag |
| * @throws IllegalArgumentException if the result class is <b>null</b> |
| */ |
| public FileBasedConfigurationBuilder(Class<? extends T> resCls, |
| Map<String, Object> params, boolean allowFailOnInit) |
| { |
| super(resCls, params, allowFailOnInit); |
| } |
| |
| /** |
| * Returns the default encoding for the specified configuration class. If an |
| * encoding has been set for the specified class (or one of its super |
| * classes), it is returned. Otherwise, result is <b>null</b>. |
| * |
| * @param configClass the configuration class in question |
| * @return the default encoding for this class (may be <b>null</b>) |
| */ |
| public static String getDefaultEncoding(Class<?> configClass) |
| { |
| String enc = DEFAULT_ENCODINGS.get(configClass); |
| if (enc != null || configClass == null) |
| { |
| return enc; |
| } |
| |
| List<Class<?>> superclasses = |
| ClassUtils.getAllSuperclasses(configClass); |
| for (Class<?> cls : superclasses) |
| { |
| enc = DEFAULT_ENCODINGS.get(cls); |
| if (enc != null) |
| { |
| return enc; |
| } |
| } |
| |
| List<Class<?>> interfaces = ClassUtils.getAllInterfaces(configClass); |
| for (Class<?> cls : interfaces) |
| { |
| enc = DEFAULT_ENCODINGS.get(cls); |
| if (enc != null) |
| { |
| return enc; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Sets a default encoding for a specific configuration class. This encoding |
| * is used if an instance of this configuration class is to be created and |
| * no encoding has been set in the parameters object for this builder. The |
| * encoding passed here not only applies to the specified class but also to |
| * its sub classes. If the encoding is <b>null</b>, it is removed. |
| * |
| * @param configClass the name of the configuration class (must not be |
| * <b>null</b>) |
| * @param encoding the default encoding for this class |
| * @throws IllegalArgumentException if the class is <b>null</b> |
| */ |
| public static void setDefaultEncoding(Class<?> configClass, String encoding) |
| { |
| if (configClass == null) |
| { |
| throw new IllegalArgumentException( |
| "Configuration class must not be null!"); |
| } |
| |
| if (encoding == null) |
| { |
| DEFAULT_ENCODINGS.remove(configClass); |
| } |
| else |
| { |
| DEFAULT_ENCODINGS.put(configClass, encoding); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} This method is overridden here to change the result type. |
| */ |
| @Override |
| public FileBasedConfigurationBuilder<T> configure( |
| BuilderParameters... params) |
| { |
| super.configure(params); |
| return this; |
| } |
| |
| /** |
| * Returns the {@code FileHandler} associated with this builder. If already |
| * a result object has been created, this {@code FileHandler} can be used to |
| * save it. Otherwise, the {@code FileHandler} from the initialization |
| * parameters is returned (which is not associated with a {@code FileBased} |
| * object). Result is never <b>null</b>. |
| * |
| * @return the {@code FileHandler} associated with this builder |
| */ |
| public synchronized FileHandler getFileHandler() |
| { |
| return (currentFileHandler != null) ? currentFileHandler |
| : fetchFileHandlerFromParameters(); |
| } |
| |
| /** |
| * {@inheritDoc} This implementation just records the fact that new |
| * parameters have been set. This means that the next time a result object |
| * is created, the {@code FileHandler} has to be initialized from |
| * initialization parameters rather than reusing the existing one. |
| */ |
| @Override |
| public synchronized BasicConfigurationBuilder<T> setParameters( |
| Map<String, Object> params) |
| { |
| super.setParameters(params); |
| resetParameters = true; |
| return this; |
| } |
| |
| /** |
| * Convenience method which saves the associated configuration. This method |
| * expects that the managed configuration has already been created and that |
| * a valid file location is available in the current {@code FileHandler}. |
| * The file handler is then used to store the configuration. |
| * |
| * @throws ConfigurationException if an error occurs |
| */ |
| public void save() throws ConfigurationException |
| { |
| getFileHandler().save(); |
| } |
| |
| /** |
| * Returns a flag whether auto save mode is currently active. |
| * |
| * @return <b>true</b> if auto save is enabled, <b>false</b> otherwise |
| */ |
| public synchronized boolean isAutoSave() |
| { |
| return autoSaveListener != null; |
| } |
| |
| /** |
| * Enables or disables auto save mode. If auto save mode is enabled, every |
| * update of the managed configuration causes it to be saved automatically; |
| * so changes are directly written to disk. |
| * |
| * @param enabled <b>true</b> if auto save mode is to be enabled, |
| * <b>false</b> otherwise |
| */ |
| public synchronized void setAutoSave(boolean enabled) |
| { |
| if (enabled) |
| { |
| installAutoSaveListener(); |
| } |
| else |
| { |
| removeAutoSaveListener(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} This implementation deals with the creation and |
| * initialization of a {@code FileHandler} associated with the new result |
| * object. |
| */ |
| @Override |
| protected void initResultInstance(T obj) throws ConfigurationException |
| { |
| super.initResultInstance(obj); |
| FileHandler srcHandler = |
| (currentFileHandler != null && !resetParameters) ? currentFileHandler |
| : fetchFileHandlerFromParameters(); |
| currentFileHandler = new FileHandler(obj, srcHandler); |
| |
| if (autoSaveListener != null) |
| { |
| autoSaveListener.updateFileHandler(currentFileHandler); |
| } |
| initFileHandler(currentFileHandler); |
| resetParameters = false; |
| } |
| |
| /** |
| * Initializes the new current {@code FileHandler}. When a new result object |
| * is created, a new {@code FileHandler} is created, too, and associated |
| * with the result object. This new handler is passed to this method. If a |
| * location is defined, the result object is loaded from this location. |
| * Note: This method is called from a synchronized block. |
| * |
| * @param handler the new current {@code FileHandler} |
| * @throws ConfigurationException if an error occurs |
| */ |
| protected void initFileHandler(FileHandler handler) |
| throws ConfigurationException |
| { |
| initEncoding(handler); |
| if (handler.isLocationDefined()) |
| { |
| handler.locate(); |
| handler.load(); |
| } |
| } |
| |
| /** |
| * Obtains the {@code FileHandler} from this builder's parameters. If no |
| * {@code FileBasedBuilderParametersImpl} object is found in this builder's |
| * parameters, a new one is created now and stored. This makes it possible |
| * to change the location of the associated file even if no parameters |
| * object was provided. |
| * |
| * @return the {@code FileHandler} from initialization parameters |
| */ |
| private FileHandler fetchFileHandlerFromParameters() |
| { |
| FileBasedBuilderParametersImpl fileParams = |
| FileBasedBuilderParametersImpl.fromParameters(getParameters(), |
| false); |
| if (fileParams == null) |
| { |
| fileParams = new FileBasedBuilderParametersImpl(); |
| addParameters(fileParams.getParameters()); |
| } |
| return fileParams.getFileHandler(); |
| } |
| |
| /** |
| * Installs the listener for the auto save mechanism if it is not yet |
| * active. |
| */ |
| private void installAutoSaveListener() |
| { |
| if (autoSaveListener == null) |
| { |
| autoSaveListener = new AutoSaveListener(this); |
| addEventListener(ConfigurationEvent.ANY, autoSaveListener); |
| autoSaveListener.updateFileHandler(getFileHandler()); |
| } |
| } |
| |
| /** |
| * Removes the listener for the auto save mechanism if it is currently |
| * active. |
| */ |
| private void removeAutoSaveListener() |
| { |
| if (autoSaveListener != null) |
| { |
| removeEventListener(ConfigurationEvent.ANY, autoSaveListener); |
| autoSaveListener.updateFileHandler(null); |
| autoSaveListener = null; |
| } |
| } |
| |
| /** |
| * Initializes the encoding of the specified file handler. If already an |
| * encoding is set, it is used. Otherwise, the default encoding for the |
| * result configuration class is obtained and set. |
| * |
| * @param handler the handler to be initialized |
| */ |
| private void initEncoding(FileHandler handler) |
| { |
| if (StringUtils.isEmpty(handler.getEncoding())) |
| { |
| String encoding = getDefaultEncoding(getResultClass()); |
| if (encoding != null) |
| { |
| handler.setEncoding(encoding); |
| } |
| } |
| } |
| |
| /** |
| * Creates a map with default encodings for configuration classes and |
| * populates it with default entries. |
| * |
| * @return the map with default encodings |
| */ |
| private static Map<Class<?>, String> initializeDefaultEncodings() |
| { |
| Map<Class<?>, String> enc = new ConcurrentHashMap<Class<?>, String>(); |
| enc.put(PropertiesConfiguration.class, |
| PropertiesConfiguration.DEFAULT_ENCODING); |
| enc.put(XMLPropertiesConfiguration.class, |
| XMLPropertiesConfiguration.DEFAULT_ENCODING); |
| return enc; |
| } |
| } |