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 -> 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}