    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2;
    019
    020import org.apache.commons.configuration2.ex.ConfigurationException;
    021import org.apache.commons.configuration2.io.ConfigurationLogger;
    022import org.apache.commons.configuration2.tree.ImmutableNode;
    023
    024import java.util.ArrayList;
    025import java.util.Collection;
    026import java.util.Collections;
    027import java.util.HashMap;
    028import java.util.List;
    029import java.util.Map;
    030
    031/**
    032 * <p>
    033 * A base class for configuration implementations based on YAML structures.
    034 * </p>
    035 * <p>
    036 * This base class offers functionality related to YAML-like data structures
    037 * based on maps. Such a map has strings as keys and arbitrary objects as
    038 * values. The class offers methods to transform such a map into a hierarchy
    039 * of {@link ImmutableNode} objects and vice versa.
    040 * </p>
    041 *
    042 * @since 2.2
    043 */
    044public class AbstractYAMLBasedConfiguration extends BaseHierarchicalConfiguration
    045{
    046    /**
    047     * Creates a new instance of {@code AbstractYAMLBasedConfiguration}.
    048     */
    049    protected AbstractYAMLBasedConfiguration()
    050    {
    051        initLogger(new ConfigurationLogger(getClass()));
    052    }
    053
    054    /**
    055     * Creates a new instance of {@code AbstractYAMLBasedConfiguration} as a
    056     * copy of the specified configuration.
    057     *
    058     * @param c the configuration to be copied
    059     */
    060    protected AbstractYAMLBasedConfiguration(
    061            final HierarchicalConfiguration<ImmutableNode> c)
    062    {
    063        super(c);
    064        initLogger(new ConfigurationLogger(getClass()));
    065    }
    066
    067    /**
    068     * Loads this configuration from the content of the specified map. The data
    069     * in the map is transformed into a hierarchy of {@link ImmutableNode}
    070     * objects.
    071     *
    072     * @param map the map to be processed
    073     */
    074    protected void load(final Map<String, Object> map)
    075    {
    076        final List<ImmutableNode> roots = constructHierarchy("", map);
    077        getNodeModel().setRootNode(roots.get(0));
    078    }
    079
    080    /**
    081     * Constructs a YAML map, i.e. String -&gt; Object from a given configuration
    082     * node.
    083     *
    084     * @param node The configuration node to create a map from.
    085     * @return A Map that contains the configuration node information.
    086     */
    087    protected Map<String, Object> constructMap(final ImmutableNode node)
    088    {
    089        final Map<String, Object> map = new HashMap<>(node.getChildren().size());
    090        for (final ImmutableNode cNode : node.getChildren())
    091        {
    092            final Object value = cNode.getChildren().isEmpty() ? cNode.getValue()
    093                    : constructMap(cNode);
    094            addEntry(map, cNode.getNodeName(), value);
    095        }
    096        return map;
    097    }
    098
    099    /**
    100     * Adds a key value pair to a map, taking list structures into account. If a
    101     * key is added which is already present in the map, this method ensures
    102     * that a list is created.
    103     *
    104     * @param map the map
    105     * @param key the key
    106     * @param value the value
    107     */
    108    private static void addEntry(final Map<String, Object> map, final String key,
    109            final Object value)
    110    {
    111        final Object oldValue = map.get(key);
    112        if (oldValue == null)
    113        {
    114            map.put(key, value);
    115        }
    116        else if (oldValue instanceof Collection)
    117        {
    118            // safe case because the collection was created by ourselves
    119            @SuppressWarnings("unchecked")
    120            final
    121            Collection<Object> values = (Collection<Object>) oldValue;
    122            values.add(value);
    123        }
    124        else
    125        {
    126            final Collection<Object> values = new ArrayList<>();
    127            values.add(oldValue);
    128            values.add(value);
    129            map.put(key, values);
    130        }
    131    }
    132
    133    /**
    134     * Creates a part of the hierarchical nodes structure of the resulting
    135     * configuration. The passed in element is converted into one or multiple
    136     * configuration nodes. (If list structures are involved, multiple nodes are
    137     * returned.)
    138     *
    139     * @param key the key of the new node(s)
    140     * @param elem the element to be processed
    141     * @return a list with configuration nodes representing the element
    142     */
    143    private static List<ImmutableNode> constructHierarchy(final String key,
    144            final Object elem)
    145    {
    146        if (elem instanceof Map)
    147        {
    148            return parseMap((Map<String, Object>) elem, key);
    149        }
    150        else if (elem instanceof Collection)
    151        {
    152            return parseCollection((Collection<Object>) elem, key);
    153        }
    154        else
    155        {
    156            return Collections.singletonList(
    157                    new ImmutableNode.Builder().name(key).value(elem).create());
    158        }
    159    }
    160
    161    /**
    162     * Parses a map structure. The single keys of the map are processed
    163     * recursively.
    164     *
    165     * @param map the map to be processed
    166     * @param key the key under which this map is to be stored
    167     * @return a node representing this map
    168     */
    169    private static List<ImmutableNode> parseMap(final Map<String, Object> map, final String key)
    170    {
    171        final ImmutableNode.Builder subtree = new ImmutableNode.Builder().name(key);
    172        for (final Map.Entry<String, Object> entry : map.entrySet())
    173        {
    174            final List<ImmutableNode> children =
    175                    constructHierarchy(entry.getKey(), entry.getValue());
    176            for (final ImmutableNode child : children)
    177            {
    178                subtree.addChild(child);
    179            }
    180        }
    181        return Collections.singletonList(subtree.create());
    182    }
    183
    184    /**
    185     * Parses a collection structure. The elements of the collection are
    186     * processed recursively.
    187     *
    188     * @param col the collection to be processed
    189     * @param key the key under which this collection is to be stored
    190     * @return a node representing this collection
    191     */
    192    private static List<ImmutableNode> parseCollection(final Collection<Object> col, final String key)
    193    {
    194        final List<ImmutableNode> nodes = new ArrayList<>(col.size());
    195        for (final Object elem : col)
    196        {
    197            nodes.addAll(constructHierarchy(key, elem));
    198        }
    199        return nodes;
    200    }
    201
    202    /**
    203     * Internal helper method to wrap an exception in a
    204     * {@code ConfigurationException}.
    205     * @param e the exception to be wrapped
    206     * @throws ConfigurationException the resulting exception
    207     */
    208    static void rethrowException(final Exception e) throws ConfigurationException
    209    {
    210        if (e instanceof ClassCastException)
    211        {
    212            throw new ConfigurationException("Error parsing", e);
    213        }
    214        throw new ConfigurationException("Unable to load the configuration",
    215                e);
    216    }
    217}