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