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 18 package org.apache.commons.configuration; 19 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.HashSet; 24 import java.util.Iterator; 25 import java.util.List; 26 import java.util.Set; 27 28 /** 29 * <p>A base class for converters that transform a normal configuration 30 * object into a hierarchical configuration.</p> 31 * <p>This class provides a default mechanism for iterating over the keys in a 32 * configuration and to throw corresponding element start and end events. By 33 * handling these events a hierarchy can be constructed that is equivalent to 34 * the keys in the original configuration.</p> 35 * <p>Concrete sub classes will implement event handlers that generate SAX 36 * events for XML processing or construct a 37 * {@code HierarchicalConfiguration} root node. All in all with this class 38 * it is possible to treat a default configuration as if it was a hierarchical 39 * configuration, which can be sometimes useful.</p> 40 * @see HierarchicalConfiguration 41 * 42 * @author <a 43 * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a> 44 * @version $Id: HierarchicalConfigurationConverter.java 1234985 2012-01-23 21:09:09Z oheger $ 45 */ 46 abstract class HierarchicalConfigurationConverter 47 { 48 /** 49 * Processes the specified configuration object. This method implements 50 * the iteration over the configuration's keys. All defined keys are 51 * translated into a set of element start and end events represented by 52 * calls to the {@code elementStart()} and 53 * {@code elementEnd()} methods. 54 * 55 * @param config the configuration to be processed 56 */ 57 public void process(Configuration config) 58 { 59 if (config != null) 60 { 61 ConfigurationKey keyEmpty = new ConfigurationKey(); 62 ConfigurationKey keyLast = keyEmpty; 63 Set<String> keySet = new HashSet<String>(); 64 65 for (Iterator<String> it = config.getKeys(); it.hasNext();) 66 { 67 String key = it.next(); 68 if (keySet.contains(key)) 69 { 70 // this key has already been processed by openElements 71 continue; 72 } 73 ConfigurationKey keyAct = new ConfigurationKey(key); 74 closeElements(keyLast, keyAct); 75 String elem = openElements(keyLast, keyAct, config, keySet); 76 fireValue(elem, config.getProperty(key)); 77 keyLast = keyAct; 78 } 79 80 // close all open 81 closeElements(keyLast, keyEmpty); 82 } 83 } 84 85 /** 86 * An event handler method that is called when an element starts. 87 * Concrete sub classes must implement it to perform a proper event 88 * handling. 89 * 90 * @param name the name of the new element 91 * @param value the element's value; can be <b>null</b> if the element 92 * does not have any value 93 */ 94 protected abstract void elementStart(String name, Object value); 95 96 /** 97 * An event handler method that is called when an element ends. For each 98 * call of {@code elementStart()} there will be a corresponding call 99 * of this method. Concrete sub classes must implement it to perform a 100 * proper event handling. 101 * 102 * @param name the name of the ending element 103 */ 104 protected abstract void elementEnd(String name); 105 106 /** 107 * Fires all necessary element end events for the specified keys. This 108 * method is called for each key obtained from the configuration to be 109 * converted. It calculates the common part of the actual and the last 110 * processed key and thus determines how many elements must be 111 * closed. 112 * 113 * @param keyLast the last processed key 114 * @param keyAct the actual key 115 */ 116 protected void closeElements(ConfigurationKey keyLast, ConfigurationKey keyAct) 117 { 118 ConfigurationKey keyDiff = keyAct.differenceKey(keyLast); 119 Iterator<String> it = reverseIterator(keyDiff); 120 if (it.hasNext()) 121 { 122 // Skip first because it has already been closed by fireValue() 123 it.next(); 124 } 125 126 while (it.hasNext()) 127 { 128 elementEnd(it.next()); 129 } 130 } 131 132 /** 133 * Helper method for determining a reverse iterator for the specified key. 134 * This implementation returns an iterator that returns the parts of the 135 * given key in reverse order, ignoring indices. 136 * 137 * @param key the key 138 * @return a reverse iterator for the parts of this key 139 */ 140 protected Iterator<String> reverseIterator(ConfigurationKey key) 141 { 142 List<String> list = new ArrayList<String>(); 143 for (ConfigurationKey.KeyIterator it = key.iterator(); it.hasNext();) 144 { 145 list.add(it.nextKey()); 146 } 147 148 Collections.reverse(list); 149 return list.iterator(); 150 } 151 152 /** 153 * Fires all necessary element start events for the specified key. This 154 * method is called for each key obtained from the configuration to be 155 * converted. It ensures that all elements "between" the last key and the 156 * actual key are opened and their values are set. 157 * 158 * @param keyLast the last processed key 159 * @param keyAct the actual key 160 * @param config the configuration to process 161 * @param keySet the set with the processed keys 162 * @return the name of the last element on the path 163 */ 164 protected String openElements(ConfigurationKey keyLast, ConfigurationKey keyAct, 165 Configuration config, Set<String> keySet) 166 { 167 ConfigurationKey.KeyIterator it = keyLast.differenceKey(keyAct).iterator(); 168 ConfigurationKey k = keyLast.commonKey(keyAct); 169 for (it.nextKey(); it.hasNext(); it.nextKey()) 170 { 171 k.append(it.currentKey(true)); 172 elementStart(it.currentKey(true), config.getProperty(k.toString())); 173 keySet.add(k.toString()); 174 } 175 return it.currentKey(true); 176 } 177 178 /** 179 * Fires all necessary element start events with the actual element values. 180 * This method is called for each key obtained from the configuration to be 181 * processed with the last part of the key as argument. The value can be 182 * either a single value or a collection. 183 * 184 * @param name the name of the actual element 185 * @param value the element's value 186 */ 187 protected void fireValue(String name, Object value) 188 { 189 if (value instanceof Collection) 190 { 191 Collection<?> valueCol = (Collection<?>) value; 192 for (Object v : valueCol) 193 { 194 fireValue(name, v); 195 } 196 } 197 else 198 { 199 elementStart(name, value); 200 elementEnd(name); 201 } 202 } 203 }