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 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(Map<String, Object> map) 075 { 076 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(ImmutableNode node) 088 { 089 Map<String, Object> map = new HashMap<>(node.getChildren().size()); 090 for (ImmutableNode cNode : node.getChildren()) 091 { 092 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(Map<String, Object> map, String key, 109 Object value) 110 { 111 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 Collection<Object> values = (Collection<Object>) oldValue; 121 values.add(value); 122 } 123 else 124 { 125 Collection<Object> values = new ArrayList<>(); 126 values.add(oldValue); 127 values.add(value); 128 map.put(key, values); 129 } 130 } 131 132 /** 133 * Creates a part of the hierarchical nodes structure of the resulting 134 * configuration. The passed in element is converted into one or multiple 135 * configuration nodes. (If list structures are involved, multiple nodes are 136 * returned.) 137 * 138 * @param key the key of the new node(s) 139 * @param elem the element to be processed 140 * @return a list with configuration nodes representing the element 141 */ 142 private static List<ImmutableNode> constructHierarchy(String key, 143 Object elem) 144 { 145 if (elem instanceof Map) 146 { 147 return parseMap((Map<String, Object>) elem, key); 148 } 149 else if (elem instanceof Collection) 150 { 151 return parseCollection((Collection<Object>) elem, key); 152 } 153 else 154 { 155 return Collections.singletonList( 156 new ImmutableNode.Builder().name(key).value(elem).create()); 157 } 158 } 159 160 /** 161 * Parses a map structure. The single keys of the map are processed 162 * recursively. 163 * 164 * @param map the map to be processed 165 * @param key the key under which this map is to be stored 166 * @return a node representing this map 167 */ 168 private static List<ImmutableNode> parseMap(Map<String, Object> map, String key) 169 { 170 ImmutableNode.Builder subtree = new ImmutableNode.Builder().name(key); 171 for (Map.Entry<String, Object> entry : map.entrySet()) 172 { 173 List<ImmutableNode> children = 174 constructHierarchy(entry.getKey(), entry.getValue()); 175 for (ImmutableNode child : children) 176 { 177 subtree.addChild(child); 178 } 179 } 180 return Collections.singletonList(subtree.create()); 181 } 182 183 /** 184 * Parses a collection structure. The elements of the collection are 185 * processed recursively. 186 * 187 * @param col the collection to be processed 188 * @param key the key under which this collection is to be stored 189 * @return a node representing this collection 190 */ 191 private static List<ImmutableNode> parseCollection(Collection<Object> col, String key) 192 { 193 List<ImmutableNode> nodes = new ArrayList<>(col.size()); 194 for (Object elem : col) 195 { 196 nodes.addAll(constructHierarchy(key, elem)); 197 } 198 return nodes; 199 } 200 201 /** 202 * Internal helper method to wrap an exception in a 203 * {@code ConfigurationException}. 204 * @param e the exception to be wrapped 205 * @throws ConfigurationException the resulting exception 206 */ 207 static void rethrowException(Exception e) throws ConfigurationException 208 { 209 if (e instanceof ClassCastException) 210 { 211 throw new ConfigurationException("Error parsing", e); 212 } 213 else 214 { 215 throw new ConfigurationException("Unable to load the configuration", 216 e); 217 } 218 } 219}