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 * https://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
18 package org.apache.commons.configuration2;
19
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.stream.Collectors;
27
28 import org.apache.commons.configuration2.ex.ConfigurationException;
29 import org.apache.commons.configuration2.io.ConfigurationLogger;
30 import org.apache.commons.configuration2.tree.ImmutableNode;
31
32 /**
33 * <p>
34 * A base class for configuration implementations based on YAML structures.
35 * </p>
36 * <p>
37 * This base class offers functionality related to YAML-like data structures based on maps. Such a map has strings as
38 * keys and arbitrary objects as values. The class offers methods to transform such a map into a hierarchy of
39 * {@link ImmutableNode} objects and vice versa.
40 * </p>
41 *
42 * @since 2.2
43 */
44 public class AbstractYAMLBasedConfiguration extends BaseHierarchicalConfiguration {
45 /**
46 * Adds a key value pair to a map, taking list structures into account. If a key is added which is already present in
47 * the map, this method ensures that a list is created.
48 *
49 * @param map the map
50 * @param key the key
51 * @param value the value
52 */
53 private static void addEntry(final Map<String, Object> map, final String key, final Object value) {
54 final Object oldValue = map.get(key);
55 if (oldValue == null) {
56 map.put(key, value);
57 } else if (oldValue instanceof Collection) {
58 // safe case because the collection was created by ourselves
59 @SuppressWarnings("unchecked")
60 final Collection<Object> values = (Collection<Object>) oldValue;
61 values.add(value);
62 } else {
63 final Collection<Object> values = new ArrayList<>();
64 values.add(oldValue);
65 values.add(value);
66 map.put(key, values);
67 }
68 }
69
70 /**
71 * Creates a part of the hierarchical nodes structure of the resulting configuration. The passed in element is converted
72 * into one or multiple configuration nodes. (If list structures are involved, multiple nodes are returned.)
73 *
74 * @param key the key of the new node(s)
75 * @param elem the element to be processed
76 * @return a list with configuration nodes representing the element
77 */
78 private static List<ImmutableNode> constructHierarchy(final String key, final Object elem) {
79 if (elem instanceof Map) {
80 return parseMap((Map<String, Object>) elem, key);
81 }
82 if (elem instanceof Collection) {
83 return parseCollection((Collection<Object>) elem, key);
84 }
85 return Collections.singletonList(new ImmutableNode.Builder().name(key).value(elem).create());
86 }
87
88 /**
89 * Parses a collection structure. The elements of the collection are processed recursively.
90 *
91 * @param col the collection to be processed
92 * @param key the key under which this collection is to be stored
93 * @return a node representing this collection
94 */
95 private static List<ImmutableNode> parseCollection(final Collection<Object> col, final String key) {
96 return col.stream().flatMap(elem -> constructHierarchy(key, elem).stream()).collect(Collectors.toList());
97 }
98
99 /**
100 * Parses a map structure. The single keys of the map are processed recursively.
101 *
102 * @param map the map to be processed
103 * @param key the key under which this map is to be stored
104 * @return a node representing this map
105 */
106 private static List<ImmutableNode> parseMap(final Map<String, Object> map, final String key) {
107 final ImmutableNode.Builder subtree = new ImmutableNode.Builder().name(key);
108 map.forEach((k, v) -> constructHierarchy(k, v).forEach(subtree::addChild));
109 return Collections.singletonList(subtree.create());
110 }
111
112 /**
113 * Internal helper method to wrap an exception in a {@code ConfigurationException}.
114 *
115 * @param e the exception to be wrapped
116 * @throws ConfigurationException the resulting exception
117 */
118 static void rethrowException(final Exception e) throws ConfigurationException {
119 if (e instanceof ClassCastException) {
120 throw new ConfigurationException("Error parsing", e);
121 }
122 throw new ConfigurationException("Unable to load the configuration", e);
123 }
124
125 /**
126 * Creates a new instance of {@code AbstractYAMLBasedConfiguration}.
127 */
128 protected AbstractYAMLBasedConfiguration() {
129 initLogger(new ConfigurationLogger(getClass()));
130 }
131
132 /**
133 * Creates a new instance of {@code AbstractYAMLBasedConfiguration} as a copy of the specified configuration.
134 *
135 * @param c the configuration to be copied
136 */
137 protected AbstractYAMLBasedConfiguration(final HierarchicalConfiguration<ImmutableNode> c) {
138 super(c);
139 initLogger(new ConfigurationLogger(getClass()));
140 }
141
142 /**
143 * Constructs a YAML map, i.e. String -> Object from a given configuration node.
144 *
145 * @param node The configuration node to create a map from.
146 * @return A Map that contains the configuration node information.
147 */
148 protected Map<String, Object> constructMap(final ImmutableNode node) {
149 final Map<String, Object> map = new HashMap<>(node.getChildren().size());
150 node.forEach(cNode -> addEntry(map, cNode.getNodeName(), cNode.getChildren().isEmpty() ? cNode.getValue() : constructMap(cNode)));
151 return map;
152 }
153
154 /**
155 * Loads this configuration from the content of the specified map. The data in the map is transformed into a hierarchy
156 * of {@link ImmutableNode} objects.
157 *
158 * @param map the map to be processed
159 */
160 protected void load(final Map<String, Object> map) {
161 final List<ImmutableNode> roots = constructHierarchy("", map);
162 getNodeModel().setRootNode(roots.get(0));
163 }
164 }