Java tutorial
/* * 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(final 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(final Class<? extends T> resCls, final 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(final Class<? extends T> resCls, final Map<String, Object> params, final 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(final Class<?> configClass) { String enc = DEFAULT_ENCODINGS.get(configClass); if (enc != null || configClass == null) { return enc; } final List<Class<?>> superclasses = ClassUtils.getAllSuperclasses(configClass); for (final Class<?> cls : superclasses) { enc = DEFAULT_ENCODINGS.get(cls); if (enc != null) { return enc; } } final List<Class<?>> interfaces = ClassUtils.getAllInterfaces(configClass); for (final 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(final Class<?> configClass, final 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(final 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(final 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(final 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(final T obj) throws ConfigurationException { super.initResultInstance(obj); final 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(final 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(final FileHandler handler) { if (StringUtils.isEmpty(handler.getEncoding())) { final 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() { final Map<Class<?>, String> enc = new ConcurrentHashMap<>(); enc.put(PropertiesConfiguration.class, PropertiesConfiguration.DEFAULT_ENCODING); enc.put(XMLPropertiesConfiguration.class, XMLPropertiesConfiguration.DEFAULT_ENCODING); return enc; } }