FileBasedConfigurationBuilder.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.builder;

  18. import java.util.Map;
  19. import java.util.concurrent.ConcurrentHashMap;

  20. import org.apache.commons.configuration2.FileBasedConfiguration;
  21. import org.apache.commons.configuration2.PropertiesConfiguration;
  22. import org.apache.commons.configuration2.XMLPropertiesConfiguration;
  23. import org.apache.commons.configuration2.event.ConfigurationEvent;
  24. import org.apache.commons.configuration2.ex.ConfigurationException;
  25. import org.apache.commons.configuration2.io.FileHandler;
  26. import org.apache.commons.lang3.ClassUtils;
  27. import org.apache.commons.lang3.StringUtils;

  28. /**
  29.  * <p>
  30.  * A specialized {@code ConfigurationBuilder} implementation which can handle configurations read from a
  31.  * {@link FileHandler}.
  32.  * </p>
  33.  * <p>
  34.  * This class extends its base class by the support of a {@link FileBasedBuilderParametersImpl} object, and especially
  35.  * of the {@link FileHandler} contained in this object. When the builder creates a new object the resulting
  36.  * {@code Configuration} instance is associated with the {@code FileHandler}. If the {@code FileHandler} has a location
  37.  * set, the {@code Configuration} is directly loaded from this location.
  38.  * </p>
  39.  * <p>
  40.  * The {@code FileHandler} is kept by this builder and can be queried later on. It can be used for instance to save the
  41.  * current {@code Configuration} after it was modified. Some care has to be taken when changing the location of the
  42.  * {@code FileHandler}: The new location is recorded and also survives an invocation of the {@code resetResult()}
  43.  * method. However, when the builder's initialization parameters are reset by calling {@code resetParameters()} the
  44.  * location is reset, too.
  45.  * </p>
  46.  *
  47.  * @param <T> the concrete type of {@code Configuration} objects created by this builder
  48.  * @since 2.0
  49.  */
  50. public class FileBasedConfigurationBuilder<T extends FileBasedConfiguration> extends BasicConfigurationBuilder<T> {
  51.     /** A map for storing default encodings for specific configuration classes. */
  52.     private static final Map<Class<?>, String> DEFAULT_ENCODINGS = initializeDefaultEncodings();

  53.     /**
  54.      * Gets the default encoding for the specified configuration class. If an encoding has been set for the specified
  55.      * class (or one of its super classes), it is returned. Otherwise, result is <strong>null</strong>.
  56.      *
  57.      * @param configClass the configuration class in question
  58.      * @return the default encoding for this class (may be <strong>null</strong>)
  59.      */
  60.     public static String getDefaultEncoding(final Class<?> configClass) {
  61.         String enc = DEFAULT_ENCODINGS.get(configClass);
  62.         if (enc != null || configClass == null) {
  63.             return enc;
  64.         }

  65.         for (final Class<?> cls : ClassUtils.getAllSuperclasses(configClass)) {
  66.             enc = DEFAULT_ENCODINGS.get(cls);
  67.             if (enc != null) {
  68.                 return enc;
  69.             }
  70.         }

  71.         for (final Class<?> cls : ClassUtils.getAllInterfaces(configClass)) {
  72.             enc = DEFAULT_ENCODINGS.get(cls);
  73.             if (enc != null) {
  74.                 return enc;
  75.             }
  76.         }

  77.         return null;
  78.     }

  79.     /**
  80.      * Creates a map with default encodings for configuration classes and populates it with default entries.
  81.      *
  82.      * @return the map with default encodings
  83.      */
  84.     private static Map<Class<?>, String> initializeDefaultEncodings() {
  85.         final Map<Class<?>, String> enc = new ConcurrentHashMap<>();
  86.         enc.put(PropertiesConfiguration.class, PropertiesConfiguration.DEFAULT_ENCODING);
  87.         enc.put(XMLPropertiesConfiguration.class, XMLPropertiesConfiguration.DEFAULT_ENCODING);
  88.         return enc;
  89.     }

  90.     /**
  91.      * Sets a default encoding for a specific configuration class. This encoding is used if an instance of this
  92.      * configuration class is to be created and no encoding has been set in the parameters object for this builder. The
  93.      * encoding passed here not only applies to the specified class but also to its sub classes. If the encoding is
  94.      * <strong>null</strong>, it is removed.
  95.      *
  96.      * @param configClass the name of the configuration class (must not be <strong>null</strong>)
  97.      * @param encoding the default encoding for this class
  98.      * @throws IllegalArgumentException if the class is <strong>null</strong>
  99.      */
  100.     public static void setDefaultEncoding(final Class<?> configClass, final String encoding) {
  101.         if (configClass == null) {
  102.             throw new IllegalArgumentException("Configuration class must not be null!");
  103.         }

  104.         if (encoding == null) {
  105.             DEFAULT_ENCODINGS.remove(configClass);
  106.         } else {
  107.             DEFAULT_ENCODINGS.put(configClass, encoding);
  108.         }
  109.     }

  110.     /** Stores the FileHandler associated with the current configuration. */
  111.     private FileHandler currentFileHandler;

  112.     /** A specialized listener for the auto save mechanism. */
  113.     private AutoSaveListener autoSaveListener;

  114.     /** A flag whether the builder's parameters were reset. */
  115.     private boolean resetParameters;

  116.     /**
  117.      * Creates a new instance of {@code FileBasedConfigurationBuilder} which produces result objects of the specified class.
  118.      *
  119.      * @param resCls the result class (must not be <strong>null</strong>
  120.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  121.      */
  122.     public FileBasedConfigurationBuilder(final Class<? extends T> resCls) {
  123.         super(resCls);
  124.     }

  125.     /**
  126.      * Creates a new instance of {@code FileBasedConfigurationBuilder} which produces result objects of the specified class
  127.      * and sets initialization parameters.
  128.      *
  129.      * @param resCls the result class (must not be <strong>null</strong>
  130.      * @param params a map with initialization parameters
  131.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  132.      */
  133.     public FileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params) {
  134.         super(resCls, params);
  135.     }

  136.     /**
  137.      * Creates a new instance of {@code FileBasedConfigurationBuilder} which produces result objects of the specified class
  138.      * and sets initialization parameters and the <em>allowFailOnInit</em> flag.
  139.      *
  140.      * @param resCls the result class (must not be <strong>null</strong>
  141.      * @param params a map with initialization parameters
  142.      * @param allowFailOnInit the <em>allowFailOnInit</em> flag
  143.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  144.      */
  145.     public FileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params, final boolean allowFailOnInit) {
  146.         super(resCls, params, allowFailOnInit);
  147.     }

  148.     /**
  149.      * {@inheritDoc} This method is overridden here to change the result type.
  150.      */
  151.     @Override
  152.     public FileBasedConfigurationBuilder<T> configure(final BuilderParameters... params) {
  153.         super.configure(params);
  154.         return this;
  155.     }

  156.     /**
  157.      * Obtains the {@code FileHandler} from this builder's parameters. If no {@code FileBasedBuilderParametersImpl} object
  158.      * is found in this builder's parameters, a new one is created now and stored. This makes it possible to change the
  159.      * location of the associated file even if no parameters object was provided.
  160.      *
  161.      * @return the {@code FileHandler} from initialization parameters
  162.      */
  163.     private FileHandler fetchFileHandlerFromParameters() {
  164.         FileBasedBuilderParametersImpl fileParams = FileBasedBuilderParametersImpl.fromParameters(getParameters(), false);
  165.         if (fileParams == null) {
  166.             fileParams = new FileBasedBuilderParametersImpl();
  167.             addParameters(fileParams.getParameters());
  168.         }
  169.         return fileParams.getFileHandler();
  170.     }

  171.     /**
  172.      * Gets the {@code FileHandler} associated with this builder. If already a result object has been created, this
  173.      * {@code FileHandler} can be used to save it. Otherwise, the {@code FileHandler} from the initialization parameters is
  174.      * returned (which is not associated with a {@code FileBased} object). Result is never <strong>null</strong>.
  175.      *
  176.      * @return the {@code FileHandler} associated with this builder
  177.      */
  178.     public synchronized FileHandler getFileHandler() {
  179.         return currentFileHandler != null ? currentFileHandler : fetchFileHandlerFromParameters();
  180.     }

  181.     /**
  182.      * Initializes the encoding of the specified file handler. If already an encoding is set, it is used. Otherwise, the
  183.      * default encoding for the result configuration class is obtained and set.
  184.      *
  185.      * @param handler the handler to be initialized
  186.      */
  187.     private void initEncoding(final FileHandler handler) {
  188.         if (StringUtils.isEmpty(handler.getEncoding())) {
  189.             final String encoding = getDefaultEncoding(getResultClass());
  190.             if (encoding != null) {
  191.                 handler.setEncoding(encoding);
  192.             }
  193.         }
  194.     }

  195.     /**
  196.      * Initializes the new current {@code FileHandler}. When a new result object is created, a new {@code FileHandler} is
  197.      * created, too, and associated with the result object. This new handler is passed to this method. If a location is
  198.      * defined, the result object is loaded from this location. Note: This method is called from a synchronized block.
  199.      *
  200.      * @param handler the new current {@code FileHandler}
  201.      * @throws ConfigurationException if an error occurs
  202.      */
  203.     protected void initFileHandler(final FileHandler handler) throws ConfigurationException {
  204.         initEncoding(handler);
  205.         if (handler.isLocationDefined()) {
  206.             handler.locate();
  207.             handler.load();
  208.         }
  209.     }

  210.     /**
  211.      * {@inheritDoc} This implementation deals with the creation and initialization of a {@code FileHandler} associated with
  212.      * the new result object.
  213.      */
  214.     @Override
  215.     protected void initResultInstance(final T obj) throws ConfigurationException {
  216.         super.initResultInstance(obj);
  217.         final FileHandler srcHandler = currentFileHandler != null && !resetParameters ? currentFileHandler : fetchFileHandlerFromParameters();
  218.         currentFileHandler = new FileHandler(obj, srcHandler);

  219.         if (autoSaveListener != null) {
  220.             autoSaveListener.updateFileHandler(currentFileHandler);
  221.         }
  222.         initFileHandler(currentFileHandler);
  223.         resetParameters = false;
  224.     }

  225.     /**
  226.      * Installs the listener for the auto save mechanism if it is not yet active.
  227.      */
  228.     private void installAutoSaveListener() {
  229.         if (autoSaveListener == null) {
  230.             autoSaveListener = new AutoSaveListener(this);
  231.             addEventListener(ConfigurationEvent.ANY, autoSaveListener);
  232.             autoSaveListener.updateFileHandler(getFileHandler());
  233.         }
  234.     }

  235.     /**
  236.      * Gets a flag whether auto save mode is currently active.
  237.      *
  238.      * @return <strong>true</strong> if auto save is enabled, <strong>false</strong> otherwise
  239.      */
  240.     public synchronized boolean isAutoSave() {
  241.         return autoSaveListener != null;
  242.     }

  243.     /**
  244.      * Removes the listener for the auto save mechanism if it is currently active.
  245.      */
  246.     private void removeAutoSaveListener() {
  247.         if (autoSaveListener != null) {
  248.             removeEventListener(ConfigurationEvent.ANY, autoSaveListener);
  249.             autoSaveListener.updateFileHandler(null);
  250.             autoSaveListener = null;
  251.         }
  252.     }

  253.     /**
  254.      * Convenience method which saves the associated configuration. This method expects that the managed configuration has
  255.      * already been created and that a valid file location is available in the current {@code FileHandler}. The file handler
  256.      * is then used to store the configuration.
  257.      *
  258.      * @throws ConfigurationException if an error occurs
  259.      */
  260.     public void save() throws ConfigurationException {
  261.         getFileHandler().save();
  262.     }

  263.     /**
  264.      * Enables or disables auto save mode. If auto save mode is enabled, every update of the managed configuration causes it
  265.      * to be saved automatically; so changes are directly written to disk.
  266.      *
  267.      * @param enabled <strong>true</strong> if auto save mode is to be enabled, <strong>false</strong> otherwise
  268.      */
  269.     public synchronized void setAutoSave(final boolean enabled) {
  270.         if (enabled) {
  271.             installAutoSaveListener();
  272.         } else {
  273.             removeAutoSaveListener();
  274.         }
  275.     }

  276.     /**
  277.      * {@inheritDoc} This implementation just records the fact that new parameters have been set. This means that the next
  278.      * time a result object is created, the {@code FileHandler} has to be initialized from initialization parameters rather
  279.      * than reusing the existing one.
  280.      */
  281.     @Override
  282.     public synchronized BasicConfigurationBuilder<T> setParameters(final Map<String, Object> params) {
  283.         super.setParameters(params);
  284.         resetParameters = true;
  285.         return this;
  286.     }
  287. }