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 }